怎么"虚拟"在C ++中对析构函数的影响?

时间:2017-08-03 02:15:57

标签: c++ undefined-behavior delete-operator vtable virtual-destructor

官方解释的虚函数是:

  

虚函数是您希望在派生类中重新定义的成员函数。当您使用指针或对基类的引用引用派生类对象时,可以为该对象调用虚函数并执行派生类的函数版本。

请先看代码:

CREATE TABLE tag (
  tag_uuid CHAR(36) PRIMARY KEY,
  name     VARCHAR(255) UNIQUE
);

CREATE TABLE type (
  type_uuid CHAR(36) PRIMARY KEY,
  name      VARCHAR(255) UNIQUE
);

CREATE TABLE type_tags (
  type_uuid CHAR(36),
  tag_uuid  CHAR(36),
  PRIMARY KEY (type_uuid, tag_uuid),
  FOREIGN KEY (type_uuid) REFERENCES type (type_uuid),
  FOREIGN KEY (tag_uuid) REFERENCES tag (tag_uuid)
);

输出是:

#include<iostream>
using namespace std;

class A
{
public:
    A(){cout << "A()" << endl;}
    ~A(){cout << "~A()" << endl;}
};

class B:public A
{
public:
    B(): A(){cout << "B()" << endl;}
    ~B(){cout << "~B()" << endl;}
};

int main()
{
    A * pt = new B;
    delete pt;
}

我的问题是:

  1. 基类的析构函数不能被派生类继承,那么为什么我们要使基类的析构函数成为虚函数呢?
  2. 对于上面的代码,我知道这会导致问题(这里没有调用B类的析构函数)。我从谷歌和stackoverflow搜索了这么多的文章或问题,所有这些都告诉我,基础的析构函数应该是虚拟的但是如何&#34;虚拟&#34;在析构函数上工作?我的意思是核心代码级别与/不是&#34;虚拟&#34;对于析构函数?

4 个答案:

答案 0 :(得分:2)

如果delete pt;的析构函数不是虚拟的,

A会导致undefined behaviour

使A析构函数为虚拟的原因是允许使用delete pt;删除B对象。

这样做的理由是,当编译器看到delete pt;时,通常无法知道pt是否指向B对象,因为该决定可能直到运行时才进行。因此,您需要查找对象的某些运行时属性(在本例中为vtable)以找出要调用的正确析构函数。

其他一些评论/答案表明原始代码的定义行为是不要调用B的析构函数或其他东西。不过那是错的。您只是看到未定义行为的症状,可能是那个或其他任何行为。

答案 1 :(得分:1)

如果析构函数被标记为虚拟,那么当您调用delete时,将调用已分配的对象的动态类型的析构函数。在您的示例中,堆上对象的静态类型是A,而动态类型是B.

由于您没有将析构函数标记为虚拟,因此不会调度运行时并调用A的析构函数。这是错误的,应该修复。如果您计划以多态方式使用类,请确保它的析构函数是虚拟的,以便派生类的实例可以释放它们已获取的任何资源。

答案 2 :(得分:1)

可以想象如何实现vtable。

具有虚方法的类具有指向函数指针表的指针作为其第一个元素。

虚拟方法意味着虚拟功能表中有一个条目。

对于方法,继承的类在覆盖时替换该条目。

对于析构函数,该条目实际上是&#34;如何在此对象上调用delete&#34;。所有下降的类都会自动覆盖它。它在概念上将delete base_ptr调用为if (base_ptr) base_ptr->vtable->deleter(base_ptr);

然后,导出的删除有效(几乎)delete static_cast<derived*>(ptr);这就像通常的删除调用一样,它按顺序调用析构函数。

如果不这样做,会留下未定义的行为。 UB通常是调用基类dtor。

答案 3 :(得分:0)

如果函数不是virtual,则C ++运行库将直接调用 mangled 函数。例如,在上面的代码中,析构函数可能会被修改为_ZNK3AXXXXXXXXX(假名)。因此,当您调用delete pt时,运行时将执行_ZNK3AXXXXXXXXX

但是,如果函数为virtual,结果会有所不同。就像@Yakk所说的那样,具有虚函数的类将具有vtable,其条目是函数指针。它可能位于此类的地址空间的顶部,也可能位于底部,具体取决于类模型的实现。任何虚拟功能的调用都将查找此表。如果dtors是virtual,派生类中的函数将覆盖表中的相应条目。当您使用指针或引用调用它时,C ++运行时将调用表中的函数。再看看你的例子。 pt指向的对象条目已被类B覆盖,因此当您调用delete pt时,将调用dtor的overrode版本。这就是区别。