David Koenig
David Koenig

Reputation: 11

Parent class using default constructor; child class' destructor is unexpectedly called

I had a scenario in C++ that calls the child's destructor in a case where I didn't expect it. A minimal repro is below:

#include <cstdio>
#include <memory>

using namespace std;

class Parent {
public:
};

class Child : public Parent {
    public:
    ~Child() {
        printf("Got here\n");
    }
};

int 
main()
{
    shared_ptr<Parent> x(new Child);
}

Usually something like this is a bug. The developer intends that the child destructor is called, and the correct action would be to insert into the parent an empty virtual destructor. However, to my shock, both G++ 4.4.7 (yeah, I know it's old) and clang 3.4.2 compile this such that the child destructor is called.

Does this conform to the standard?

Upvotes: 0

Views: 84

Answers (2)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275976

A unique ptr would screw up here. But shared ptr does some "magic" here.

shared_ptr<T> has two things they manage; theT*` and the reference counting block.

The reference counting block contains two atomic<std::size_t>s, one for strong references and one for weak references, and a type-erased deleter. This std::function-like type-erased deleter remembers how to delete the thing you own.

When you construct any shared_ptr with a U*u, it by default stores [u]{std::default_delete<U>{}(u);} in that deleter.

In effect, it remembers how to delete the object based on the type passed in.

shared_ptr is exceedingly flexible.

You can pass in a custom deleter to replace the default one, you can use the aliasing constructor to split the reference counting block used from the T* stored, you can use make_shared to allocate the reference counting block and a T in the same memory allocation.

The overhead of the reference counting block is why it stores the deleter; it wasn't viewed as overly expensive, given that we needed the block anyhow. In comparison, unique_ptr by default does no such thing; you have to explicitly add a deleter, and you'd have to manage all of the fancy tricks that shared_ptr does for you by default if you wanted them. unique_ptr has basically zero overhead over a raw owning pointer; shared_ptr has noticable overhead, but small compared to memory allocation overhead usually.

Upvotes: 0

Mark B
Mark B

Reputation: 96311

Well even if shared_ptr didn't have special magic, deleteing by parent pointer with non-virtual destructor is just undefined behavior so the results (of calling the child destructor) would definitely be conforming.

But in this case shared_ptr "remembers" the type of the original object you passed into it and destroys it by child pointer (through its stored deleter).

Upvotes: 5

Related Questions