考虑以下功能完备的示例:
#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
的列表与继承相结合?
答案 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周期,还会破坏您的缓存局部性。
最后,您应该考虑继承,组合或模板是否是解决问题的更好方法;每个都有权衡。