Search code examples
c++memorypolymorphismshared-ptrvirtual-destructor

Is there any advantage if virtual destructor is not defined with shared_ptr


When we use shared_ptr with polymorphic classes we don't need virtual destructors due to type erased deleter.

But does it makes sense to define a destructor in simple cases.

What can be the advantages of not declaring a destructor at all?

Consider the below code

struct Base {                                                                   
  /*virtual*/ ~Base() { std::cout << "Base Dtor" << std::endl; }  
               OR
  /*virtual*/ ~Base() = default;              
  virtual void foo() = 0;                                                       
};                                                                              
                                                                                
struct Derived : Base {                                                         
  ~Derived() { std::cout << "Derived Dtor" << std::endl; }                      
  void foo() override { std::cout << "foo" << std::endl; }                      
};                                                                              
                                                                                
int main() {                                                                    
  std::shared_ptr<Base> ptr = std::make_shared<Derived>();                      
}

Solution

  • Yes, of course there are advantages to not having a virtual dtor even for a polymorphic type:

    1. Removed dynamic dispatch, allowing for more efficient static dispatch or even easy inlining. Making the compiler prove it and reduce dynamic to static dispatch on its own is non-trivial.

    2. Removed the entries for deallocating-dtor and just-dtor from the vtable. Thus, if either is actually unused that's far easier proved, facilitating dead code elimination.

    In the grand scheme of things, both effects are generally miniscule though:

    1. The one dynamic dispatch (at worst) is dwarfed by the cost of deallocating.

    2. One of the two (deallocating-dtor and just-dtor) will be needed if there are any instances, and having both is a trivial amount of additional instructions as the former delegates to the latter. It is barely worth thinking about.

    On the other hand, not having a base with virtual dtor can be really dangerous if you (or someone else) slips up (or ignores policy) and writes code dependent on that. Undefined behavior is rarely fun.
    Which is the reason everyone always insists that if any dynamic polymorphism happens (any virtual function, dtor, base, or base with the same), then the dtor had better be virtual too!