jeudi 26 octobre 2017

C++11 iterator and the scope of a returned std::unique_ptr

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