在派生类中指定基类的C++顺序
C++ order of specifying base classes in derived class
这可能是一个基本问题,但我在任何地方都没有看到。在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
继承的类的顺序是B
、C
时,它似乎起作用的原因可能是,在您的情况下,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 C
和class D : public C, public B
在构造D
时交换基类构造的顺序。
- std::具有相同基类的类的变体
- 是否可以初始化不可复制类型的成员变量(或基类)
- 在C++中,是否可以基于给定的标识符创建基类的新实例,反之亦然
- 基类中的函数名称解析
- C++初始化基类
- 如何通过派生类函数更改基类中的向量
- 如何定义一个纯抽象基类
- 如何使用基类指针引用派生类成员
- 继承:构造函数,初始化C++11中基类的类C数组成员
- 使用基类指针创建对象时,缺少派生类析构函数
- 如何引用基类的派生类?
- 如果基类包含双指针成员,则派生类的构造函数
- 在模板基类中为继承类中的可选重写生成虚拟方法
- 为什么此派生对象无法访问基类的后递减方法?
- 公开最直接的基类模板名称
- 当基类是依赖类型时,这是一个缺陷吗
- 如何使基类的运算符对基类的可变参数数可见(请参阅下面的代码)?
- 模板基类中的静态变量
- C++ 继承:将子类传递给需要基类的函数并获取子类行为
- 继承和友元函数,从基类访问受保护的成员