在派生类中指定基类的C++顺序

C++ order of specifying base classes in derived class

本文关键字:基类 C++ 顺序 派生      更新时间:2023-10-16

这可能是一个基本问题,但我在任何地方都没有看到。在C++中,假设我有以下代码:

#include <iostream>
using namespace std;
class B  {
public:
void f();
};
class C  {
public:
void f();
};
class D : public B, public C{
public:
void f();
};
void B::f(){cout << "bbb" << endl;}
void C::f(){cout << "ccc" << endl;}
void D::f(){cout << "ddd" << endl;}
int main () {
B *d = new D;
delete d;
d->f();
return 0;
}

这可以很好地工作并输出"bbb">。但如果我切换的顺序

class D : public B, public C{

class D : public C, public B{

我会得到一个"中止(核心转储)">错误。这是什么原因?有人能进一步解释基类的顺序在哪些方面很重要吗?

我知道这是多重继承,但我正在避免钻石问题,所以不确定发生了什么。

B不是多态的(例如,不提供任何virtual方法)。因此,编译器假设d所指向的对象的动态类型为B,并尝试根据该B对象的地址删除内存,该地址不再与实际D对象的地址匹配。正如人们所写:

D * d = new D;
delete static_cast<B *>(d);

未定义的行为就在那里。当D继承的类的顺序是BC时,它似乎起作用的原因可能是,在您的情况下,D对象的B部分的地址恰好与D对象本身的地址相同。没有定义析构函数(显式或隐式),因此只调用了一个释放函数,该函数可能接受了相应分配函数提供的指针。

另请注意,根据〔basic.life〕

类似地,在对象的生存期开始之前,但在分配对象将占用的存储之后,或者在对象的生命期结束之后,在重用或释放对象所占用的存储之前,可以使用任何引用原始对象的glvalue,但只能以有限的方式使用。对于正在构建或销毁的对象,请参阅[class.cdtor]。否则,这样的glvalue指的是已分配的存储([basic.stc.dynamic.deallosition]),并且使用glvalue的不依赖于其值的属性是定义良好的。程序有未定义的行为,如果:

  • glvalue用于访问对象,或者
  • glvalue用于调用对象的非静态成员函数,或者
  • glvalue绑定到对虚拟基类([dcl.init.ref])的引用,或者
  • glvalue用作dynamic_cast的操作数或typeid的操作数

因此,在代码中的delete d;之后调用d->f();也会导致未定义的行为。

这是未定义的行为,纯粹而简单。

一旦你有了delete,你就不能使用d。诚然,它以一种方式"工作"而不是另一种方式是奇怪的,但这就是未定义行为的本质。(你可以推测原因,注意第一种情况下D的地址是B的地址-注意你的类不是多态类型-,但在第二种情况下不是。delete只是释放内存,而不是立即擦除它)。

但实际上,

delete d;
d->f();

不会有好的结局。即使您交换了这些语句的顺序,也应该使基类析构函数成为虚拟的,以便删除正确的内存。

class D : public B, public Cclass D : public C, public B在构造D时交换基类构造的顺序