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

时间:2013-05-30 15:04:59

标签: 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() {       
        std::cout << "B() \n";
    }
    ~B() {
        std::cout << "~B() \n";
    }
};

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

    return 0;
}

输出结果为:

Output: 
A() 
B() 
~A 

有没有办法让B的析构函数像这样被继承调用?我不知道unique_ptrs也有切片问题。当然我可以使用std::unique_ptr<B>,但我希望有一个std::vector<std::unique_ptr<A>>并添加继承的项目。

有没有办法让std::unique_ptr的列表与继承相结合?

2 个答案:

答案 0 :(得分:9)

当您说delete p;且包含*p的最派生对象的类型(俗称&#34;动态类型*p&#34;)不一样作为*p的静态类型,如果*p的静态类型是类类型并且没有虚拟析构函数,则行为是未定义的。

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

答案 1 :(得分:0)

@ user2384250真正的问题似乎是虚拟调度不是默认值的原因。

TLDR:您需要预先支付性能损失(在呼叫站点,对于您创建的每个实例以及由于破坏缓存局部性而在程序范围内)。如果默认情况下所有函数都进行了虚拟调度,那么这是一个你无法实现的惩罚(没有更尴尬的语法)。

如果您不在课堂的任何地方使用虚拟发送,那么您的课程将获得最佳性能。即使B继承自A,如果A没有任何虚方法,那么编译器也无法区分B&amp; B的实例。一个;如果你有一个变量A* instance;&amp;你打电话给instance->foo(),编译器无法知道你下面有一个B&amp;它会调用A::foo()

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

正如您所看到的,添加甚至1个虚拟方法的前期隐藏成本与调用虚方法无关;你会得到每班1个查询表和你的类的每个实例都会被1个指针变大。此外,编译器无法提前知道您是否要创建子类(虚拟表指针位于您首先声明方法虚拟的类中)。如果您希望默认行为是虚拟调度,程序中的每个类都将不必要地支付这种性能损失。

此外,由于上面的机制,虚拟方法稍微昂贵:而不是编译器插入指令:跳转到函数foo(),它必须:为此实例加载虚拟指针,为函数添加偏移量foo(),取消引用该条目(函数的地址)&amp;跳到它。这不仅涉及更多的CPU周期,还会破坏您的缓存局部性。

最后,您应该考虑继承,组合或模板是否是解决问题的更好方法;每个都有权衡。