【问题标题】:C++ Virtual Destructors in a 4 level inheritance chain.4 级继承链中的 C++ 虚拟析构函数。
【发布时间】:2012-06-18 18:49:22
【问题描述】:

我正在用虚拟析构函数做一个小实验来复习 - 想知道是否有人对以下内容有简单的解释(使用 vs 2010):

我定义类层次A-B-C-D,D继承C,C继承B,B继承A,A是Base;

进行了 2 次实验:

第一次实验-

A 有一个虚拟析构函数。

B 有一个非虚拟析构函数

C 有一个虚拟析构函数

D 有一个非虚析构函数

//----------------------------

在 D 类型的堆上分配 4 个对象 - 在前 3 个指向 A*、B* 和 C* 的指针 - 将第 4 个保留为 D* 以保证完整性。 删除所有 4 个指针。

正如我所料,在所有 4 个实例中,完整的析构函数链都以从 D 到 A 的相反顺序执行,释放所有内存。

第二次实验-

A 有一个非虚拟析构函数 ** 将 A 更改为非虚拟

B 有一个非虚拟析构函数

C 有一个虚拟析构函数

D 有一个非虚析构函数

在 D 类型的堆上分配 4 个对象 - 在前 3 个指向 A*、B* 和 C* 的指针 - 将第 4 个保留为 D* 以保证完整性。

删除 C* 和 D* 指针: 完整的析构链从 D 到 A 以相反的顺序执行,释放所有内存。

删除 B*: B 然后运行 ​​A 析构函数(泄漏)

删除 A*: 只运行一个析构函数(泄漏)

谁能解释这是为什么?

当在实验 2 中分配 D 类型对象时,它的直接基类 (C) 有一个虚拟析构函数 - 不是告诉编译器用 Vptr 跟踪它并知道内存类型吗?不考虑参考文献?

谢谢 迈克

【问题讨论】:

  • 如果您提供了示例代码,那就太好了。
  • @Rook 我已经发布了一个代码 sn-p 来说明这种情况(几乎相同) - 如果有任何 cmets,请告诉我。

标签: c++ virtual-destructor


【解决方案1】:

当在实验 2 中分配 D 类型对象时,它的直接基类 (C) 有一个虚拟析构函数 - 不是告诉编译器用 Vptr 跟踪它并知道内存类型吗?不考虑参考文献?

没有。

在您的第二个测试用例中,AB 没有 vptrs/vtables。 (即使他们这样做了,非虚拟成员函数仍然会被静态解析,而不是动态解析。)

换句话说,基类不会从派生类“继承”信息(例如函数是否为虚函数)。

【讨论】:

  • 如果基类不从派生类继承信息,为什么在实验 1 中,将 B 引用指向 D 对象(B 具有非虚拟析构函数)仍然“知道”它是 D 对象为 D 类型对象分配并运行整个析构函数序列?
  • 因为在第一个测试用例中,B 的析构函数虚拟的;如果一个成员函数在基类中是虚函数,那么它在派生类中也是虚函数,即使您没有显式声明它也是如此。
  • 好吧,这是有道理的——这就是我的误解所在——
  • @OliverCharlesworth:我认为这只适用于被覆盖的方法,这也适用于析构函数?我认为您必须明确声明链中的所有析构函数为虚拟的。如果不是这种情况,那很高兴知道。
  • @tjwrona1992:只要方法(包括 dtor)被声明为虚拟的,它对于所有子类也是隐式虚拟的。
【解决方案2】:

当您删除没有虚拟析构函数的 A* 时,编译器在编译时不知道它将在运行时指向具有虚拟析构函数的对象。删除可能是具有虚拟析构函数的对象——也可能不是。不会发生动态绑定。

【讨论】:

  • OK - 你是说,引用(因为它是 A 类指针)不知道在编译时,它会指向一个带有虚拟析构函数的对象?
  • (对不起,上面的评论不完整) - 然而,在实验一中,在编译时,B 对象在其基类中有一个虚拟析构函数 - 那么当 B 指向 D 时,会发生动态绑定吗?由此我可以得出以下结论吗? 1 - 它是决定动态绑定的引用(指针类型) - 而不是实际分配的内存,以及 2 - 只要在继承链中的某个地方在编译时存在虚拟析构函数,就会发生动态绑定?跨度>
  • 乐于助人。 1 - 是; 2 - 在引用类型之上的某个地方,包括在内,是的。
【解决方案3】:

您的真正问题是为什么要使用虚拟和非虚拟析构函数?因为有一个带有非虚拟析构函数的基类是不好的。见the faq

【讨论】:

  • 它读起来很像演示非虚拟析构函数带来的问题。他描述的所有行为似乎都是意料之中的。
  • 否 - 但我错过了常见问题解答中的注释 -
【解决方案4】:

我写了几乎相同的问题,所以我想分享它。

请注意,我还在不同的 Ctor 中添加了一些虚函数的用法,以说明它是如何工作的(很快,在每个 Ctor 中,V-table 仅更新“由它决定”,这意味着虚函数实现将被调用的是继承链的“这一点”之前最派生的)。

我的注释:在运行给定类的示例代码中,我还在堆栈上添加了“派生”对象(B 和 D)的创建 --> 以强调有关“虚拟”的所有注意事项当我们使用指向类实例的指针(任何类型的)时,Dtor 的“-ness”是适用的。

class A;

void callBack(A const& a);

class A 
{

    public:
    A() { std::cout << "A Ctor " << std::endl; f1(); callBack(*this); /* f3();*/ }
    ~A() { std::cout << "A Dtor " << std::endl; }

    void f1() { std::cout << "A : f1 " << std::endl; }
    virtual void f2() const { std::cout << "A : f2 " << std::endl; }
    virtual void f3() = 0;
};


class B : public A 
{
    public: 
    B() { std::cout << "B Ctor " << std::endl;  f1(); callBack(*this); f3(); }
    ~B() { std::cout << "B Dtor " << std::endl; }
    void f1 () { std::cout << "B : f1 " << std::endl;}
    void f2() const { std::cout << "B : f2 " << std::endl; }
    virtual void f3() { std::cout << "B : f3 " << std::endl; }

};


class C : public A 
{
    public:
    C() { std::cout << "C Ctor " << std::endl; f1(); callBack(*this); f3(); }
    virtual ~C() { std::cout << "C Dtor " << std::endl; }
    void f1() { std::cout << "C : f1" << std::endl;}
    void f2() const { std::cout << "C : f2" << std::endl; }
    virtual void f3() const { std::cout << "C : f3" << std::endl; }

};

class D : public C 
{
    public:
    D() { std::cout << "D Ctor " << std::endl;  f1(); callBack(*this); }
    ~D() { std::cout << "D Dtor " << std::endl; }
    void f1() { std::cout << "D : f1" << std::endl; }
    void f2() const { std::cout << "D : f2 " << std::endl; }
    virtual void f3() { std::cout << "D : f3 " << std::endl; }

};

void callBack(A const& a) { a.f2(); }

// =================================================================================================================================

int main()
{
    std::cout << "Start of main program" << std::endl;

    std::cout << "Creating a D object on the heap" << std::endl;
    D* pd = new D;
    C* pc = new D;
    A* pa = new D;

    if (true)
    {
        std::cout << "Entering Dummy scope # 1 and creating B object on the stack" << std::endl;
        B b;
        std::cout << "Leaving Dummy scope # 1 with B object within it" << std::endl;
    }

    if (true)
    {
        std::cout << "Entering Dummy scope # 2 and creating D object on the stack" << std::endl;
        D d;
        std::cout << "Leaving Dummy scope # 2 with D object within it" << std::endl;
    }

    std::cout << "Calling delete on pd (D*) which points on a D object" << std::endl;
    delete pd;

    std::cout << "Calling delete on pc (C*) which points on a D object" << std::endl;
    delete pc;

    std::cout << "Calling delete on pa (A*) which points on a D object" << std::endl;
    delete pa;

   std::cout << "End of main program" << std::endl;
   return 0;
}

【讨论】:

    猜你喜欢
    • 2016-08-14
    • 2011-01-12
    • 1970-01-01
    • 2015-06-21
    • 2020-03-10
    • 1970-01-01
    • 2017-12-05
    • 2019-10-11
    • 1970-01-01
    相关资源
    最近更新 更多