简体   繁体   中英

std::unique_ptr inheritance slicing and destructors

Consider the following fully functioning example:

#include <iostream>
#include <memory>

class A {
    public:
    A() {
        std::cout << "A() \n";
    }
    ~A() {
        std::cout << "~A \n";
    }

};
class B:public A {
    public:
    B() {       
        std::cout << "B() \n";
    }
    ~B() {
        std::cout << "~B() \n";
    }
};

int main() {
    std::cout << "Output: \n";
    {
        std::unique_ptr<A> TestB(new B());
    }

    return 0;
}

The output is:

Output: 
A() 
B() 
~A 

Is there any way for B 's destructor to be called with inheritance like this? I was not aware that unique_ptrs also have slicing problem. Of course I can use std::unique_ptr<B> but I wanted to have a std::vector<std::unique_ptr<A>> and add inherited items.

Is there a way to have a list of std::unique_ptr s in combination with inheritance?

When you say delete p; and the type of the most-derived object containing *p (colloquially "the dynamic type of *p ") is not the same as the static type of *p , then the behaviour is undefined if the static type of *p is a class-type and does not have a virtual destructor.

To fix this, you need to say virtual ~A() .

@user2384250's real question appears to be why virtual dispatch isn't the default.

TLDR: There are performance penalties you would pay up-front (at the call-site, for every instance you create & program-wide due to ruining cache locality). This is a penalty you would not really be able to recoup (without even more awkward syntax) if all functions did virtual-dispatch by default.

If you don't use virtual dispatch anywhere in your class, then your class will have the best performance possible. Even if B inherits from A, if A doesn't have any virtual methods, then the compiler can't distinguish between instances of B & A; if you have a variable A* instance; & you call instance->foo() , the compiler can't know that you have a B underneath & it will invoke A::foo() .

When you declare foo() virtual in A, the compiler creates a virtual table for A, inserts foo() into that virtual table & adds a hidden virtual table pointer to the class. Then, on every invocation of foo(), it knows that it needs to perform virtual dispatch (since foo() is declared virtual). It will load the look-up-table given by the pointer & invoke the foo() that it's told about there. That way, when you have an instance of B, the pointer will point to the lookup-table for the B class & when you have an instance of A, it will point to the instance of A; thus regardless of whether instance is an A* or a B*, the compiler will just load the lookup table & invoke the foo that's in the dispatch table regardless of the type declared at the call-site.

As you can see, adding even 1 virtual method has a hidden cost upfront that is independent of invoking the virtual method; you'll get 1 lookup table per-class & every instance of your class will be bigger by 1 pointer. Additionally, the compiler can't know ahead of time whether or not you're ever going to create a subclass (the virtual table pointer lives inside the class where you first declare the method virtual). If you wanted the default behaviour to be virtual dispatch, every class in your program would pay this performance penalty needlessly.

Additionally, virtual methods, due to the mechanism above, are slightly more expensive: instead of the compiler inserting an instruction: jump to function foo(), it has to: load the virtual pointer for this instance, add offset for function foo(), dereference that entry (address of the function) & jump to it. This not only involves more CPU cycles, it also ruins your cache locality.

Finally, you should really think about whether inheritance, composition, or templates are the better solution to the problem; there are tradeoffs with each.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM