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

C++ std::unique_ptr继承切片和析构函数,c++,c++11,C++,C++11,考虑以下功能完备的示例: #include <iostream> #include <memory> class A { public: A() { std::cout << "A() \n"; } ~A() { std::cout << "~A \n"; } }; class B:public A { public: B() {

考虑以下功能完备的示例:

#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;
}
有没有办法用这样的继承调用
B
的析构函数?我不知道unique_PTR也有切片问题。当然,我可以使用
std::unique\u ptr
,但我想要一个
std::vector
,并添加继承项


有没有一种方法可以将
std::unique_ptr
s列表与继承结合起来?

当你说
delete p
和包含
*p
的最派生对象的类型(俗称“动态类型的
*p
”)与
*p
的静态类型不同,如果
*p
的静态类型是类类型并且没有虚拟析构函数,则行为是未定义的


要解决这个问题,您需要说
virtual~A()

@user2384250的真正问题似乎是为什么虚拟分派不是默认值

TLDR:您将提前支付性能损失(在调用站点,由于破坏缓存位置而创建的每个实例和程序范围内的实例)。如果默认情况下所有函数都执行虚拟分派,那么这将是一个无法收回的惩罚(没有更糟糕的语法)

如果您在类中的任何位置都不使用虚拟分派,那么您的类将具有尽可能最好的性能。即使B从A继承,如果A没有任何虚拟方法,编译器也无法区分B和A的实例;如果您有一个变量
a*实例&你调用
实例->foo()
,编译器无法知道你下面有一个B&它将调用
a::foo()

在A中声明
foo()
virtual
时,编译器将为A创建一个虚拟表,将
foo()
插入该虚拟表并添加指向该类的隐藏虚拟表指针。然后,在每次调用foo()时,它都知道需要执行虚拟分派(因为foo()被声明为虚拟的)。它将加载指针给出的查找表&调用它在那里被告知的foo()。这样,当您有一个B实例时,指针将指向B类的查找表&当您有一个A实例时,它将指向A实例;因此,无论
instance
是A*还是B*,编译器都将只加载查找表并调用调度表中的foo,而不管调用站点上声明的类型如何

如您所见,即使添加一个虚拟方法,也会有一个独立于调用虚拟方法的隐藏开销;每个类将得到1个查找表&类的每个实例都将增加1个指针。此外,编译器无法提前知道是否要创建子类(虚拟表指针位于您第一次声明虚拟方法的类中)。如果您希望默认行为是虚拟分派,那么程序中的每个类都将不必要地支付此性能惩罚

此外,由于上述机制,虚拟方法的成本稍高一些:编译器没有插入指令:跳转到函数foo(),而是必须:加载此实例的虚拟指针,为函数foo()添加偏移量,取消引用该项(函数的地址)并跳转到它。这不仅会占用更多的CPU周期,还会破坏缓存位置


最后,您应该真正考虑继承、组合还是模板是解决问题的更好方法;每个都有折衷。

你需要使
~A()
虚拟
@juanchopanza我也在想同样的事情,这是正确的答案,谢谢。UB没有虚拟析构函数…这是真的,但我不明白为什么,我的意思是,如果我声明一个对象
bk
,其中
B
是从
A
派生出来的,那么为什么调用
A
的析构函数是“自然”的呢?这里有一个明显的优先级,或者可能是因为C++支持多重继承…@ USER884250:与这些无关。问题是您的类型是
唯一的\u ptr
。如果有一个
唯一的\u ptr
就可以了。也许我没有正确地解释我自己,我正在考虑我刚刚声明
bk在问题帖子中定义的类,我不能解释它是如何用C++来设计这种方式以及它是如何操作它的行为的,当我声明“代码> B K<代码>代码> k>代码>被破坏时,<代码> ~A < /C>也被调用,这是我真正不能得到的部分。如果你只声明<代码> BK;<代码>,没有问题。只有当您试图通过指向基子对象的指针销毁动态分配的对象时,才会出现问题,原因很明显:子对象必须有某种机制知道它是较大对象的一部分。我知道这没有问题,我的问题是Bjarne S.在设计这个的时候是怎么想的,我也是这么看的,B比A大,所以在类型B的对象上调用2析构函数~B和~A对我来说毫无意义,析构函数~A能做什么~B不能做,因为B是比A大的类型?
Output: 
A() 
B() 
~A