Home > Back-end >  C : Implementation of virtual destructor necessary when using inherited structs with only propertie
C : Implementation of virtual destructor necessary when using inherited structs with only propertie

Time:03-31

I know that I need to define a virtual destructor (best option even if my class is final). In my case, I am using C-like structures (no functions, no defaults, just plain members) and use inheritance to compose a new structure. I then store a pointer to the base class in std::unique_ptr and let RAII do the rest.

I am now curious if there is a need to also explicitely add a virtual destructor to avoid memory problems.

An example might be:

#include <chrono>
#include <memory>

struct A {
    std::chrono::milliseconds duration = std::chrono::milliseconds{-1};
    int count = 0;
};

struct B {
    int mode = 0;
};

struct C : public A, public B {
    int foo = 1;
};

int main()
{
   std::unique_ptr<A> base = std::make_unique<C>();
   base.reset(); // I expect here that A,B and C are destructed properly

   return 0;
}

CodePudding user response:

It doesn't matter whether the class is polymorphic or whether it is trivial.

If delete is called on a pointer of different type (up to cv-qualification) than the most-derived type of the object it points to and the pointed-to-type doesn't have a virtual destructor, then the behavior is undefined.

One obvious reason for this rule is that the base class subobject might not be located at the same address as the most-derived object. So the compiler would have no way of knowing what the offset to pass to the deallocation function needs to be.

One could maybe argue that a standard-layout class with trivial destructor would not need to follow this rule if a pointer to the first base class subobject is used, but the standard doesn't make that exception and your class C isn't standard-layout anyway.

See CWG issue 1259 closed as not-a-defect. The size-aware global deallocation functions mentioned in the issue were also introduced with C 14, which is another reason that using a base class pointer may cause you problems in practice, even if the destructor is trivial and the address without offset.

CodePudding user response:

If you use shared_ptr rather than unique_ptr then this will work as the deleter of the shared_ptr is created when the first pointer is created and doesn't change when the pointer is copied between shared_ptr instances. This doesn't apply in unique_ptr which will use std::default_delete<T> where T is the type of the unique_ptr instance not the type originally constructed.

See https://godbolt.org/z/TjP6dbo9G for some examples of unique_ptr failing and shared_ptr working.

  • Related