高端网站建设上,改革开放40周年网站发展建设,住房公积金网站怎么做减员,郑州最出名的不孕不育医院在C中#xff0c;菱形继承的内存模型会因是否使用虚继承产生本质差异。我们通过具体示例说明两种场景的区别#xff1a; 一、普通菱形继承的内存模型
class A { int a; };
class B : public A { int b; };
class C : public A { int c; };
class D : public B, public C { i…在C中菱形继承的内存模型会因是否使用虚继承产生本质差异。我们通过具体示例说明两种场景的区别 一、普通菱形继承的内存模型
class A { int a; };
class B : public A { int b; };
class C : public A { int c; };
class D : public B, public C { int d; };内存布局特点
|-------------------|
| B::A::a (4字节) |
| B::b (4字节) |
|-------------------|
| C::A::a (4字节) |
| C::c (4字节) |
|-------------------|
| D::d (4字节) |
|-------------------|关键问题
冗余存储派生类D包含两份A的成员变量B::A::a 和 C::A::a访问二义性d.a 需要明确指定路径d.B::a 或 d.C::a 二、虚继承后的内存模型
class A { int a; };
class B : virtual public A { int b; };
class C : virtual public A { int c; };
class D : public B, public C { int d; };典型内存布局以GCC为例
|-------------------|
| B::vbptr (8字节*) | ➝ 虚基类表记录A的偏移量
| B::b (4字节) |
|-------------------|
| C::vbptr (8字节*) | ➝ 同样指向A的偏移量
| C::c (4字节) |
|-------------------|
| D::d (4字节) |
|-------------------|
| A::a (4字节) | ← 唯一一份A的成员
| Padding (4字节) | 对齐填充
|-------------------|核心变化
共享基类虚基类A的成员a在D中只有一份间接访问通过虚基类指针vbptr定位共享的A实例初始化责任D的构造函数直接初始化A 三、关键差异对比
特征普通继承虚继承基类冗余存储存在两份A共享唯一A实例派生类大小较大含重复数据较小但含指针开销访问基类成员直接访问通过虚基类表间接访问初始化方式中间类负责初始化最终派生类负责初始化 四、验证示例
#include iostream
using namespace std;class A { public: int a; };
class B : virtual public A { public: int b; };
class C : virtual public A { public: int c; };
class D : public B, public C { public: int d; };int main() {D d;d.B::a 1; // 虚继承后修改的是同一份A::ad.C::a 2; cout d.B::a; // 输出2证明A是共享的
}五、注意在虚继承情况下虚基类的构造由最底层的派生类直接负责而不是由中间的基类来构造的。
六、典型应用
在C标准库中经典的虚继承解决菱形继承的案例体现在输入输出流iostream库的实现中。以下是具体分析 标准库中的流类继承体系 basic_ios...↑ ↑虚| |虚| |basic_istream... basic_ostream...↖ ↗basic_iostream...关键结构解析
**基类 **basic_ios 所有流类的公共基类负责管理流的状态如错误标志、格式化设置等。**中间派生类 basic_istream 和 **basic_ostream basic_istream输入流通过虚继承派生自 basic_iosbasic_ostream输出流通过虚继承派生自 basic_ios **最终派生类 **basic_iostream 同时继承 basic_istream 和 basic_ostream需确保 basic_ios 仅存在一份实例。 虚继承的作用
避免菱形继承的二义性 若 basic_istream 和 basic_ostream 未虚继承 basic_ios则 basic_iostream 将包含两个独立的 basic_ios 实例导致访问公共成员如 good()、setf()时出现二义性。确保单一共享基类 通过虚继承basic_iostream 仅保留一个 basic_ios 实例避免冗余存储和成员冲突。 验证虚继承的示例
#include iostreamint main() {std::iostream io std::cin; // 合法std::cin是std::istream但向上转型安全io.get(); // 正确调用basic_ios的成员无二义性return 0;
}构造顺序 basic_iostream 的构造函数直接初始化虚基类 basic_ios确保基类仅构造一次。 标准库实现代码片段简化
// 基类
templatetypename CharT, typename Traits
class basic_ios : public ios_base { /*...*/ };// 输入流虚继承
templatetypename CharT, typename Traits
class basic_istream : virtual public basic_iosCharT, Traits { /*...*/ };// 输出流虚继承
templatetypename CharT, typename Traits
class basic_ostream : virtual public basic_iosCharT, Traits { /*...*/ };// 最终流
templatetypename CharT, typename Traits
class basic_iostream : public basic_istreamCharT, Traits,public basic_ostreamCharT, Traits {
public:// 显式调用虚基类构造函数explicit basic_iostream(/*...*/) : basic_iosCharT, Traits(/*...*/),basic_istreamCharT, Traits(/*...*/),basic_ostreamCharT, Traits(/*...*/) {}
};总结
普通菱形继承基类冗余存储存在数据冗余和二义性。虚继承通过虚基类指针共享唯一基类牺牲间接访问性能换取空间和语义统一。编译器通过虚基类表如GCC的vbptr管理偏移量确保派生类正确访问共享基类。最后尽量不使用菱形继承 ● 组合代替继承将共享功能封装为工具类通过对象组合调用。 ● 接口分离将基类拆分为多个职责单一的接口避免多重继承。 ● 依赖注入通过参数传递依赖对象而非直接继承。