6

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?

Community
  • 1
  • 1
Thomas B.
  • 2,176
  • 13
  • 22
  • 1
    `std::auto_ptr` is deprecated in C++11 (http://www.cplusplus.com/reference/memory/auto_ptr/) because smart pointers were introduced and I don't think its a good idea to ever use it. – Gasim Aug 08 '14 at 13:22
  • About the checked delete, you would still need it for the raw pointer as its the raw pointer. But after doing a test, shared_ptr and unique_ptr do delete correctly. Here is the test code (http://ideone.com/0sSnpZ). – Gasim Aug 08 '14 at 13:29
  • 2
    @Gasim Test code passing does not prove anything about undefined behavior. Answering this will require reading the STL specification. – ComputerDruid Aug 08 '14 at 14:23
  • @Gasim: Because *better* smart pointers were introduced. (And new language features, to make `unique_ptr` work) `std::auto_ptr` is classified as a smart pointer. – Ben Voigt Aug 08 '14 at 19:51
  • possible duplicate of [Is std::unique\_ptr required to know the full definition of T?](http://stackoverflow.com/questions/6012157/is-stdunique-ptrt-required-to-know-the-full-definition-of-t) – Ben Voigt Aug 08 '14 at 19:56

1 Answers1

4

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.

Jonathan Wakely
  • 153,269
  • 21
  • 303
  • 482
  • "GCC instantiates templates at the end of the file"... I'm not sure what you mean by this. If you mean in a later phase of compilation, after semantic analysis of the entire file, ok, but it still has to only consider definitions which were in scope at the time implicit instantiation occurred, in order to remain Standard compliant. – Ben Voigt Aug 08 '14 at 14:47
  • I don't mean a later phase, I mean that for some instantiations the [point of instantiation is at the end of the file](http://open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#993), which can have surprising effects sometimes. But in this case I think it's where `b` is defined. – Jonathan Wakely Aug 08 '14 at 15:00
  • Regarding the "not 100% safety": also the checked-delete idiom won't help in case of `base* b = new derived()` with non-virtual destructors, so that's ok. But are there any other pitfalls regarding object destruction if I stick to `unique_ptr`s and virtual-destructors? – Thomas B. Aug 08 '14 at 15:04