C++虚基类

C++虚基类

C++虚基类

在多继承时很容易产生命名冲突问题,如果我们很小心地将所有类中的成员变量及成员函数都命名为不同的名字时,命名冲突依然有可能发生,比如非常经典的菱形继承结构。

所谓菱形继承,举个例子,类 A 派生出类 B 和类 C,类 D 继承自类 B 和类 C,这个时候类 A 中的成员变量和成员函数继承到类 D 中变成了两份,一份来自 A 派生 B 然后派生 D 这一路,另一份来自 A 派生 C 然后派生 D 这一条路。
在这里插入图片描述

class A
{
public:
    void setx(int a){x = a;}
    int getx(){return x;}
private:
    int x;
};
class B: public A
{
public:
    void sety(int a){y = a;}
    int gety(){return y;}
private:
    int y;   
};
class C: public A
{
public:
    void setz(int a){z = a;}
    int getz(){return z;}
private:
    int z;
};
class D: public B, public C
{
    //......
};

本例即为典型的菱形继承结构,类 A 中的成员变量及成员函数继承到类 D 中均会产生两份,这样的命名冲突非常的棘手,通过域解析操作符已经无法分清具体的变量了。为此,C++ 提供了虚继承这一方式解决命名冲突问题。虚继承只需要在继承属性前加上 virtual 关键字。

#include <iostream>
using namespace std;

class A
{
public:
    void setx(int a){x = a;}
    int getx(){return x;}
private:
    int x;
};

class B: virtual public A
{
public:
    void sety(int a){y = a;}
    int gety(){return y;}
private:
    int y;
};

class C: virtual public A
{
public:
    void setz(int a){z = a;}
    int getz(){return z;}
private:
    int z;
};

class D: public B, public C
{
public:
    void setd(int a){d = a;}
    int getd(){return d;}
private:
    int d;
};

int main()
{
    D test;
    test.setx(10);
    cout<<test.getx()<<endl;
    return 0;
}

在本例中,类 B 和类 C 都以虚继承的方式继承类 A,如此操作之后,类 D 只会得到一份来自类 A 的数据。主函数中定义了类 D 的对象 test,然后通过该对象调用从类 A 间接继承来的 setx() 和 getx() 成员函数,因为 B 和 C 继承自类 A 采用的是虚继承,故通过 D 调用 setx() 和 getx() 不会有命名冲突问题,因为 D 类只得到了一份 A 的数据。

reference

微学苑 C++ 虚继承

### C++虚基类的使用方法及注意事项 #### 一、虚基类的作用 虚基类的主要目的是为了防止多重继承时产生的二义性和重复数据成员问题。当一个派生通过多个路径继承同一个基时,如果不使用继承,则会创建该基的多个实例副本[^2]。 #### 二、虚基类的定义方式 要实现继承,在声明派生时需在 `class` 关键字后面加上关键字 `virtual` 和 `public` 或其他访问修饰符。例如: ```cpp class Base {}; class Derived1 : virtual public Base {}; // 继承 class Derived2 : virtual public Base {}; // 继承 class FinalDerived : public Derived1, public Derived2 {}; ``` 在此例子中,由于 `Derived1` 和 `Derived2` 都是从 `Base` 继承而来的,所以最终的 `FinalDerived` 只有一个 `Base` 的子对象[^2]。 #### 三、初始化顺序 对于虚基类,其构造函数会在任何非虚基类之前被调用。即使某个中间派生未显式调用虚基类的构造函数,编译器也会自动为其提供默认参数来完成初始化过程。以下是具体示例代码及其解释: ```cpp #include <iostream> using namespace std; class A { protected: int value; public: A(int v = 0) : value(v) { cout << "A Constructor Called with Value: " << value << endl; } }; class B : virtual public A { public: B() : A(1) { cout << "B Constructor Called" << endl; } }; class C : virtual public A { public: C() : A(2) { cout << "C Constructor Called" << endl; } }; class D : public B, public C { public: D() : A(3), B(), C() { cout << "D Constructor Called" << endl; } }; int main(){ D obj; } ``` 在这个程序里,尽管 `B` 和 `C` 各自尝试设置不同的初始值给它们共同的拟父级 `A` ,但由于存在更具体的指令——即来自最底层衍生别 `D` 所指定之数值 (此处为3),故此优先采用后者作为实际传递至 `A` 构造函式的参数[^3]。 #### 四、内存布局特点 因为引入了额外的信息用于管理共享的基础结构体实例位置关系等原因,通常情况下运用到基础型的实体相较于单纯线性单一层次体系架构下的同等情况会有更大的存储需求量以及稍微复杂一点的操作逻辑[^2]。 #### 五、动态型转换中的表现形式 如果涉及到从含有根节点的对象模型内部提取特定种别的指针或者引用操作的话,那么就可能需要用到诸如 `dynamic_cast<>` 运算符来进行安全可靠的转型处理动作[^4]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值