Gazoo Gazoo - 8 months ago 28
C++ Question

Deleting derived classes in std::unique_ptr<Base> containers

I'm a little confused. Basically, I've got 2 different resource managers (AudioLibrary and VideoLibrary) that both inherit from a shared BaseLibrary class. This base class contains references to both audio and video. Both audio and video inherit from a parent class called Media.

I'm keeping the data in a map, filled with unique_ptr. But, to my surprise, I've discovered when these pointers are eventually deleted via .erase, only the base destructor for Media is called.

I guess I've missed something, but I thought that the compiler/run-time library would know that it's either pointing to a video or audio and call it's destructor.

Seems not to be the case. I'm forced to do something like this to actually reclaim all my resources:

void AudioLibrary::deleteStream( const std::string &pathFile )
auto baseStream = mStreams[ pathFile ].release();
mStreams.erase( pathFile );

// Re-cast!
auto aStream = static_cast<AudioStream*>( baseStream );
delete aStream;

Is this normal behavior?


You're all right - of course it is the missing 'virtual'ness of the destructor. I guess I've recently just thought less and less of what virtual and inheritance means and kind of gotten my head lost in its functionality, rather than the concepts themselves. It happens for me occasionally.


The default deleter for unique_ptr<T> is the aptly named default_delete<T>. This is a stateless functor that calls delete on its T * argument.

If you want the correct destructor to be called when a unique_ptr to a base class is destructed, you must either use a virtual destructor, or capture the derived type in a deleter.

You can do this quite easily using a function pointer deleter and captureless lambda:

std::unique_ptr<B, void (*)(B *)> pb
    = std::unique_ptr<D, void (*)(B *)>(new D,
        [](B *p){ delete static_cast<D *>(p); });

Of course, this means that you need to add the template argument for your deleter to all uses of unique_ptr. Encapsulating this in another class might be more elegant.

An alternative to this is to use shared_ptr, as that does capture the derived type, as long as you create the derived shared_ptr using std::shared_ptr<D>(...) or, preferably, std::make_shared<D>(...).