mardi 26 mai 2015

Why doesn't this RAII move-only type properly emulate `std::unique_ptr`?

I took the code from this question and edited it to produce a segfault by explicitly calling the destructor of one of the move-constructed objects:

using namespace std;

struct Foo
{
    Foo()  
    {
        s = new char[100]; 
        cout << "Constructor called!" << endl;  
    }

    Foo(const Foo& f) = delete;

    Foo(Foo&& f) :
      s{f.s}
    {
        cout << "Move ctor called!" << endl;   
        f.s = nullptr;
    }

    ~Foo() 
    { 
        cout << "Destructor called!" << endl;   
        cout << "s null? " << (s == nullptr) << endl;
        delete[] s; // okay if s is NULL
    }

    char* s;
};

void work(Foo&& f2)
{
    cout << "About to create f3..." << endl;
    Foo f3(move(f2));
    // f3.~Foo();
}

int main()
{
    Foo f1;
    work(move(f1));
}

Compiling and running this code (with G++ 4.9) produces the following output:

Constructor called!
About to create f3...
Move ctor called!
Destructor called!
s null? 0
Destructor called!
s null? 0
*** glibc detected *** ./a.out: double free or corruption (!prev): 0x0916a060 ***

Note that when the destructor is not explicitly called, no double-free error occurs.

Now, when I change the type of s to unique_ptr<char[]> and remove the delete[] s in ~Foo() and f.s = nullptr in Foo(Foo&&), I do not get a double-free error:

Constructor called!
About to create f3...
Move ctor called!
Destructor called!
s null? 0
Destructor called!
s null? 1
Destructor called!
s null? 1

What is going on here? Why can the moved-to object be explicitly deleted when its data member is a unique_ptr, but not when the invalidation of the moved-from object is handled manually in Foo(Foo&&)? Since the move-constructor is called when f3 is created (as shown by the "Move ctor called!" line), why does the first destructor call (presumably for f3) state that s is not null? If the answer is simply that f3 and f2 are somehow actually the same object due to an optimization, what is unique_ptr doing that's preventing the same problem from happening with that implementation?

Aucun commentaire:

Enregistrer un commentaire