3

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?

Update:

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.

Gazoo
  • 215
  • 4
  • 14
  • 4
    *"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."* No. You're missing a *virtual destructor*. You should probably look into a basic C++ OOP book. You could also search for that term *virtual destructor*. – dyp Jun 05 '14 at 12:45
  • did you make the base destructor `virtual`? – Kai Walz Jun 05 '14 at 12:46
  • Possible duplicate of http://stackoverflow.com/q/1315534 and/or http://stackoverflow.com/q/20573587 (one of them is the first hit on google for "derived class destructor not called") – dyp Jun 05 '14 at 12:48
  • have unmarked as duplicate: the case of `unique_ptr` is a bit different to a raw pointer (and especially, the possible solutions). One might expect that the smart pointer has a smart deleter, like `shared_ptr` does. – M.M Aug 26 '16 at 01:22

2 Answers2

8

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>(...).

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • @dyp it's more general (works with multiple inheritance etc.) and the difference in safety is minimal (as `unique_ptr` templated move constructor is doing most of the work anyway). – ecatmur Jun 05 '14 at 14:44
  • @dyp you're right, I got myself confused. In the general case you'd need to capture the original pointer as well as its deleter; of course, this is what `shared_ptr` does when aliasing. – ecatmur Jun 05 '14 at 15:11
3

This is due to the fact that you haven't declared your Media destructor virtual. As you can see, if you do, for example:

struct Media {
    virtual ~Media() = default;
};

struct AudioLibrary : Media {};
struct VideoLibrary : Media {};

int main() {
    std::map<int, std::unique_ptr<Media>> map;
    map[0] = std::unique_ptr<Media>(new AudioLibrary());
    map[1] = std::unique_ptr<Media>(new VideoLibrary());
}

demo

both destructors will be called.

Shoe
  • 74,840
  • 36
  • 166
  • 272