Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple inheritance and unique_ptr destruction

I have the classic (possible problematic) multiple inheritance diamond scheme.

  • B inherits A
  • C inherits A
  • D inherits C and B

I want to have a std::vector that can contain either C or D objects so I make it as std::vector<C> which is D's dad and it works fine.

BUT when I use: std::vector<std::unique_ptr<C>> then I have segmentation fault upon the destruction of the vector.

** glibc detected *** ./a.out: free(): invalid pointer: 0x0000000009948018***

Why is there a difference? To me, even the first implementation is problematic.

Code

#include <string>
#include <vector>
#include <memory>

class A
{
public:
    A() = default;
};

class B : public virtual A
{
public:
    B() = default;
};

class C : public virtual A
{
public:
    C() = default;
};

class D : public B, public C
{
public:
    D() = default;
};


int main()
{
    { // this crashes
    std::vector<std::unique_ptr<C>> v;
    std::unique_ptr<D> s1(new D());
    v.push_back(std::move(s1));
    std::unique_ptr<C> s2(new C());
    v.push_back(std::move(s2));
    }

    { // this is fine
    std::vector<C> v;
    D s1;
    v.push_back(s1);
    C s2;
    v.push_back(s2);
    }

    return 0;
};
like image 325
Dimitris Dakopoulos Avatar asked Aug 24 '16 13:08

Dimitris Dakopoulos


People also ask

How destructors are called with multiple inheritance?

Destructors are called in the order "most derived to most basal", and in reverse order of declaration. So ~AB is called first, then ~B , then ~A , because AB is the most derived class.

What is the use of declaring virtual destructor under multiple inheritances?

In simple terms, a virtual destructor ensures that when derived subclasses go out of scope or are deleted the order of destruction of each class in a hierarchy is carried out correctly. If the destruction order of the class objects is incorrect, in can lead to what is known as a memory leak.

Does Unique_ptr call destructor?

Yes. Well the unique ptr has a function object that by default invokes delete on the pointed to object, which calls the destructor. You can change the type of that default deleter to do almost anything.

Why is Unique_ptr useful?

Use unique_ptr when you want to have single ownership(Exclusive) of the resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another. A shared_ptr is a container for raw pointers.

How to destroy a dynamic object with a unique_ptr?

Thus, when you use a unique_ptr, you do not have to delete dynamically allocated objects, and the destructor of unique_ptr will take care of that. Below is an illustrated code that shows the scope outside which the pointer is destroyed. The object is destroyed when the control goes out of these curly braces.

What happens when you delete a unique_ptr?

the managing unique_ptr object is destroyed the managing unique_ptr object is assigned another pointer via operator= or reset (). The object is disposed of, using a potentially user-supplied deleter by calling get_deleter()(ptr). The default deleter uses the delete operator, which destroys the object and deallocates the memory.

What is the difference between shared_ptr and unique_ptr?

Unlike std::shared_ptr, std::unique_ptr may manage an object through any custom handle type that satisfies NullablePointer. This allows, for example, managing objects located in shared memory, by supplying a Deleter that defines typedef boost::offset_ptr pointer; or another fancy pointer .

What is unique_ptr in C++?

(2) (since C++11) std::unique_ptr is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr goes out of scope.


Video Answer


2 Answers

You should declare your destructors as virtual. Otherwise, if your class D is deleted using a pointer to C, then the ~C() destructor will be called and essential parts of the cleanup will be missed.

Also note that in your second part (not using the unique_ptr) you do some object slicing. It means you are creating a copy of s1 of type D into a new object of class C, hence you can lose the extra information specific to D.

Here is the corrected code :

#include <string>
#include <vector>
#include <memory>

class A
{
public:
    A() = default;
    virtual ~A() {};
};

class B : public virtual A
{
public:
    B() = default;
    virtual ~B() {};
};

class C : public virtual A
{
public:
    C() = default;
    virtual ~C() {};
};

class D : public B, public C
{
public:
    D() = default;
    virtual ~D() {};
};


int main()
{
    { // this does not crashe anymore
        std::vector<std::unique_ptr<C>> v;
        std::unique_ptr<D> s1(new D());
        v.push_back(std::move(s1));
        std::unique_ptr<C> s2(new C());
        v.push_back(std::move(s2));
    }

    { // this is fine because you slice D into C, still that fine ?
        std::vector<C> v;
        D s1;
        v.push_back(s1);
        C s2;
        v.push_back(s2);
    }

    return 0;
}

See also

  • When to use virtual destructors?
  • What is object slicing?

Note

As stated in the comments, if you mark A destructor's as virtual, every derived class will also have a virtual destructor. Writing it everywhere can make it more explicit. It is a matter of style.

like image 140
dkg Avatar answered Nov 08 '22 20:11

dkg


Your "this is fine" example does slicing, the vector only contains instances of C, that's why it 'works' but does not do what you expected. The solution is as dkg points out is to use virtual dtors.

like image 22
Alexander Balabin Avatar answered Nov 08 '22 19:11

Alexander Balabin