Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you forget about Checked-Delete when using C++11 smart pointers?

I've read about unique_ptr with incomplete types and about Checked Delete. But is checked-delete obsolete when using smart pointers, or at least a subset of C++11's smart pointers?

Take the following code:

class A;

class B
{
public:
    std::auto_ptr<A> autoPtr;
    std::unique_ptr<A> uniquePtr;
    std::shared_ptr<A> sharedPtr;
    A* rawPtr;

    B();
    ~B(){delete rawPtr;}
};

class A
{
public:
    ~A(){std::cout << "~A" << std::endl;}
};

B::B()
{
    autoPtr = std::auto_ptr<A>(new A());
    uniquePtr = std::unique_ptr<A>(new A());
    sharedPtr = std::shared_ptr<A>(new A());
    rawPtr = new A();
}

B b;

When B's destructor is defined, the type of A is still incomplete. As far as I know [C++11 standard @ [expr.delete]] the deletion of the raw pointer is undefined behaviour. On my machine, gcc 4.8 shows some warnings about this but compiles and the A's destructor is not called appropriately.

But what about the smart pointers? As I've read, unique_ptr and shared_ptr must work according to the c++11 standard. But both linked documents state, that auto_ptr won't work. But at least with my gcc 4.8 also auto_ptr correctly calls the destructor, without any warning. Is this still undefined behaviour and gcc is just nice?

In short: Which of the four member variables are guaranteed to destroy their A-pointer appropriately using it's later defined destructor, according to the C++11 standard?

And finally: If I only use unique_ptr and shared_ptr and never call "delete" by myself, am I safe and never need to think about "checked delete" again?

like image 712
Thomas B. Avatar asked Aug 08 '14 13:08

Thomas B.


1 Answers

For shared_ptr what matters is whether the type is complete when you pass the pointer to the shared_ptr (which it usually should be, because you've just created the A). At that point the shared_ptr will create some kind of deleter, which needs the complete type, and will store it away so it is available when the reference count drops to zero (even if that happens in a file where A is incomplete).

For unique_ptr what matters is whether the type is complete when the default_delete is invoked, which will usually be in the unique_ptr destructor (but can also be in its reset() member or assignment operator).

Using auto_ptr with incomplete types is just undefined.

To answer the question in the title: the standard requires that shared_ptr and unique_ptr will refuse to compile code that would try to delete an incomplete type. That doesn't guarantee 100% safety, because doing unique_ptr<base>(new derived) can still have undefined behaviour if base does not have a virtual destructor.

I believe what's happening to allow your code to compile is that GCC instantiates templates at the end of the file, and the inline B destructor is not needed until b is defined, at which point A is complete. If you put the definition of A and B::B in a separate file from the variable b then you would find that the unique_ptr would not correctly delete the A when b is destroyed (in fact it shouldn't even compile, because destroying b invokes default_delete and the standard says that requires the a complete type or the program is ill-formed).

The portable solution is to ensure that A is complete where you define the destructor for B, so do not define the destructor inline if A will not be complete in all files.

like image 162
Jonathan Wakely Avatar answered Oct 04 '22 21:10

Jonathan Wakely