简体   繁体   English

std :: unique_ptr继承切片和析构函数

[英]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? 有没有办法让B的析构函数像这样被继承调用? I was not aware that unique_ptrs also have slicing problem. 我不知道unique_ptrs也有切片问题。 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. 当然我可以使用std::unique_ptr<B>但我希望有一个std::vector<std::unique_ptr<A>>并添加继承的项目。

Is there a way to have a list of std::unique_ptr s in combination with inheritance? 有没有办法将std::unique_ptr列表与继承结合使用?

When you say delete p; 当你说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. 和含有最派生的对象的类型*p (俗称“动态类型的*p ”)是不一样的静态类型的*p ,则该行为是未定义如果静态类型的*p是一类-type并没有虚拟析构函数。

To fix this, you need to say virtual ~A() . 要解决这个问题,你需要说virtual ~A()

@user2384250's real question appears to be why virtual dispatch isn't the default. @ user2384250的真正问题似乎是虚拟调度不是默认值的原因。

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). TLDR:您需要预先支付性能损失(在呼叫站点,对于您创建的每个实例以及由于破坏缓存局部性而在程序范围内)。 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; 即使B继承自A,如果A没有任何虚方法,那么编译器也无法区分B&A的实例; if you have a variable A* instance; 如果你有一个变量A* instance; & you call instance->foo() , the compiler can't know that you have a B underneath & it will invoke A::foo() . 你调用了instance->foo() ,编译器无法知道你下面有一个B,它会调用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. 在A中声明foo() virtual时,编译器会为A创建一个虚拟表,将foo()插入该虚拟表中,并向该类添加一个隐藏的虚拟表指针。 Then, on every invocation of foo(), it knows that it needs to perform virtual dispatch (since foo() is declared virtual). 然后,在每次调用foo()时,它都知道它需要执行虚拟调度(因为foo()被声明为virtual)。 It will load the look-up-table given by the pointer & invoke the foo() that it's told about there. 它将加载指针给出的查找表并调用它所告知的foo()。 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; 这样,当你有一个B实例时,指针将指向B类的查找表,当你有一个A实例时,它将指向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. 因此,无论instance是A *还是B *,编译器都只会加载查找表并调用调度表中的foo,而不管调用站点声明的类型如何。

As you can see, adding even 1 virtual method has a hidden cost upfront that is independent of invoking the virtual method; 正如您所看到的,添加甚至1个虚拟方法的前期隐藏成本与调用虚方法无关; you'll get 1 lookup table per-class & every instance of your class will be bigger by 1 pointer. 你会得到每个类1个查找表,你的类的每个实例将更大1指针。 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. 另外,由于上面的机制,虚拟方法稍贵一些:而不是编译器插入指令:跳转到函数foo(),它必须:加载此实例的虚拟指针,为函数foo()添加偏移量,取消引用该条目(函数的地址)并跳转到它。 This not only involves more CPU cycles, it also ruins your cache locality. 这不仅涉及更多的CPU周期,还会破坏您的缓存局部性。

Finally, you should really think about whether inheritance, composition, or templates are the better solution to the problem; 最后,您应该考虑继承,组合或模板是否是解决问题的更好方法; there are tradeoffs with each. 每个都有权衡。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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