Problem
As I understand it, when a std::unique_ptr
is returned from a function into an rvalue, its lifetime should encompass the statement that consumes that rvalue. But compiling with gcc 6.4.1, the return value from Foo::iterator()
goes out of scope before the start of the C++11 foreach statement in function crashing_version()
. As shown in the output below, the destructor is called just after the containing expression is evaluated. Is this a bug in gcc, or bad programming practice?
Use Case
The goal of this pattern is to make iteration available without exposing the private vectors. This seems to require some object like Foo::Iterator
because there are two separate lists to iterate.
#include <iostream>
#include <memory>
#include <vector>
class Foo {
/* Goal: allow iteration without exposing the vector objects. */
std::vector<int> _list0;
std::vector<int> _list1;
public:
class Iterator {
int _list_id;
Foo& _foo;
public:
Iterator(int list_id, Foo& foo) : _list_id(list_id), _foo(foo) {}
~Iterator() {
std::cout << "~Iterator(): Destroying iterator of the "
<< (_list_id == 0 ? "even" : "odd") << " list\n";
}
std::vector<int>::iterator begin() {
if (_list_id == 0)
return _foo._list0.begin();
else
return _foo._list1.begin();
}
std::vector<int>::iterator end() {
if (_list_id == 0)
return _foo._list0.end();
else
return _foo._list1.end();
}
};
void add(int i) {
if ((i % 2) == 0)
_list0.push_back(i);
else
_list1.push_back(i);
}
std::unique_ptr<Iterator> iterator(int list_id) {
return std::make_unique<Iterator>(list_id, *this);
}
};
void working_version() {
Foo foo;
for (int i = 0; i < 10; i++)
foo.add(i);
/* This works because the unique_ptr stays in scope through the loop. */
std::cout << "Valid iterator usage: \n";
std::unique_ptr<Foo::Iterator> evens = foo.iterator(0);
for (int i : *evens)
std::cout << i << "\n";
}
void crashing_version() {
Foo foo;
for (int i = 0; i < 10; i++)
foo.add(i);
/* Crash! The unique_ptr goes out of scope before the loop starts. */
std::cout << "Corrupt iterator usage: \n";
for (int i : *foo.iterator(1))
std::cout << i << "\n";
}
int main() {
working_version();
crashing_version();
return 0;
}
Program output:
Valid iterator usage:
0
2
4
6
8
~Iterator(): Destroying iterator of the even list
Corrupt iterator usage:
~Iterator(): Destroying iterator of the odd list
1
3
5
7
9
Aucun commentaire:
Enregistrer un commentaire