3

考虑以下功能齐全的示例:

#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像这样继承调用 's 的析构函数?我不知道 unique_ptrs 也有切片问题。当然我可以使用std::unique_ptr<B>,但我想要一个std::vector<std::unique_ptr<A>>并添加继承的项目。

有没有办法将std::unique_ptrs 列表与继承结合起来?

4

2 回答 2

9

当您说delete p;包含的最派生对象的*p类型(通俗地说“动态类型*p”)与 的静态类型不同时,*p如果静态类型*p是类类型并且不有一个虚拟析构函数。

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

于 2013-05-30T15:13:13.453 回答
0

@user2384250 的真正问题似乎是为什么虚拟调度不是默认设置。

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

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

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

如您所见,即使添加 1 个虚拟方法也会产生与调用虚拟方法无关的隐藏成本;您将获得每个类 1 个查找表,并且您的类的每个实例都会大 1 个指针。此外,编译器无法提前知道您是否要创建子类(虚拟表指针位于您首先声明虚拟方法的类中)。如果您希望默认行为是虚拟分派,那么程序中的每个类都会不必要地付出这种性能损失。

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

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

于 2013-11-18T21:13:12.197 回答