虚拟继承,虚基类

本文详细介绍了C++中虚拟继承的概念及其应用场景,旨在解决由多个父类的共同基类产生的二义性问题,并通过示例代码展示了如何实现虚拟继承。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

摘自:http://blog.youkuaiyun.com/skylor/archive/2009/03/26/4025698.aspx

 

虚拟继承与虚基类实际上是说了同一件事,只是不同的书表达不同,在这里还是推荐虚拟继承这种说法(因为有人总问虚基类是什么,这里可以解释为虚基类就是虚拟继承,一种继承的方式,有的书偏要把一个动作写成一个名词,不负责任)。虚拟继承是C++继承的一个特殊方法,用来达到特殊的目的。要达到什么目的呢?那就是避免继承机制下的二义性问题(二义性:程序产生两种或多种可能,把编译器搞的不知所措)

 

继承机制下的二义性一般体现在两种情况下,要介绍的虚拟继承主要解决了其中第二种情况的二义性问题,不妨把两种情况都简单说一说:

 

【第一种情况】:由多个基类同名成员产生的二义性

 

 

class A           //定义一个类A

{

    public:

        A(){cout << "A called"<< endl;}

        void print(){cout << "A print" <<endl;}

    private:

   

};

 

class B          //定义一个类 B

{

    public:

        B(){cout << "B called" << endl;}

        void print(){cout << "B print" << endl;}

    private:

           

};

class C :public A , public B   //定义一个类分别继承自 A B

{

       public:

        C(){}

    private:

};

int main(void)

{

C c;

c.print(); ------------------------------------------------------- mark 1

       getchar();

return 0

}

如上图和代码所示:主程序main在执行到mark1标记时产生了二义性,c对象有两个基类,编译器不知道该调用Aprint() 还是Bprint(),这个二义性的产生的解决办法与虚拟继承无关,需要用作用于符号来解决,即将c.print() 修改为 c.A::print();那么就调用Aprint方法,也就是告诉他要调用的方法在哪个基类里。

 

【第二种情况】:由多个父类的共同基类产生的二义性

 

AB,C的共同基类,D继承于B,C

class A

{

    public:

        A(){cout << "A called"<< endl;}

        void print(){cout << "A print" <<endl;}

    private:

   

};

 

class B :  public A

{

    public:

        B(){cout << "B called" << endl;}

       

    private:

           

};

class C :  public A

{

    public:

        C(){cout << "C called" << endl;}

           private:

           

};

class D:public B,public C

{

    public:

        D(){cout << "D called" << endl;}

    private:

           

};

int main(void)

{

    D d;

    d.print();---------------------------------------------------------mark 2

    getchar();

    return 0;

}

如上图和代码所示:主程序main在执行到mar2标记时产生了二义性,虽然只有基类A中有print(),但是继承的路线有两条,编译器不知道从B路线向上找还是从C向上找,一样会出错,这里可以用第一种方法用到的作用于说明符号即将d.print()改为d.B::print(),告诉编译器是从B继承下来的,当然,也可以改为d.C::print()。除了这种解决方案,还有另外一种解决方案,就是:运用虚拟继承的机制。实际上造成上边的二义性的根本原因是在这种继承的特殊模式下,A这个父类分别伴随BC产生了两个拷贝,在调用拷贝中的方法时产生了矛盾,到底是调用哪一个拷贝中的print()呢?于是,所有人都会想,要是只有一个拷贝就好了,就没有矛盾了,虚拟继承就提供了这种机制,按上面的例子,只需修改BCA的继承方式,即加一个关键字 virtual

class B :  virtual public A

{

    public:

        B(){cout << "B called" << endl;}

       

    private:

           

};

class C :  virtual public A

{

    public:

        C(){cout << "C called" << endl;}

           private:

           

};

这样就相当于说,在没有A类的拷贝时就构造一个,如果已经有了,就用已经有的那一个,这样一来,拷贝只有一份了,二义性消除了。

 

虚拟继承不多说了,最后在补充点关于继承的东西,实际上继承框架性的东西不多,一个访问控制,一个调用顺序,把这两个搞清楚,再把上面的弄明白,就差我下面要说的一件事了,

有的地方叫继承的支配规则:

派生类中设置了基类中同名的成员,就是说基类中的成员的名字在派生类中再次使用,则派生类中的名字就把基类的名字隐藏在其后面了,所有的调用都是对派生类成员的调用,除非用域说明符 :: 。

上例子吧:

【例一】

class A

{

    public:

        A(){cout << "A called"<< endl;}

        void print(){cout << "A print" <<endl;}

    private:

   

};

 

class B :  public A

{

    public:

        B(){cout << "B called" << endl;}

        void print(){cout << "B print" << endl;}

    private:

           

};

int main(void)

{

    B b;

    b.print();

    getchar();

    return 0;

}

打印的肯定是"B print",那要想打印"A print"呢,只能改为b.A::print();

 

 

需要说明的是这里并不是对print的重载(根本是同一个方法嘛,与重载没关系),应该起名叫“隐藏”比较合适。

【例二】

class A

{

    public:

        A(){cout << "A called"<< endl;}

        void print(){cout << "A print" <<endl;}

    private:

   

};

 

class B :  public A

{

    public:

        B(){cout << "B called" << endl;}

        void print(int a){cout << "B print" << endl;}

    private:

           

};

int main(void)

{

    B b;

    b.print();

    getchar();

    return 0;

}

这个例子编译一下,没通过!怎么可能呢,Bprint参数不符合,不是有基类的print符合要求嘛,怎么不调用呢,原来在派生类B中,print被更改了,这里是被重载了,很多人把这叫“覆盖”,有点道理,所以A类中的print()怎么也访问不到,除非用作用域符号,怎么改呢,加个参数,加个作用域符,看你想怎么用了,再这里只想说明“覆盖”这么件事。

 

### C++ 中虚基类和虚继承的概念及用法 #### 虚基类的概念与作用 在C++中,虚基类是一种特殊的继承方式,用于解决多重继承带来的二义性和重复数据成员的问题。当一个派生类从多个基类派生而来,而这些基类又共同继承同一个祖先类时,可能会导致该祖先类被多次实例化的情况。这种现象被称为“菱形继承问题”。为了避免这种情况的发生,C++提供了虚继承机制[^1]。 通过使用`virtual`关键字修饰继承关系,可以让所有间接派生的子类共享同一份基类实例,从而消除冗余的数据成员并简化程序逻辑[^3]。 #### 虚继承的应用场景 虚继承主要应用于复杂的类层次结构中,特别是涉及多重继承的情况下。例如,在设计图形界面库或者游戏引擎等大型软件项目时,经常会出现这样的需求——某些功能模块可能需要同时具备多种行为特征(即它们是从不同父类派生出来的),但又要确保不会因为过度复制而导致资源浪费或操作混乱[^2]。 下面是一个典型的例子: ```cpp class Base { public: int baseValue; }; // 定义两个中间层类,并都采用虚拟方式继承自Base class DerivedA : virtual public Base {}; class DerivedB : virtual public Base {}; // 终极派生类同时继承于DerivedA和DerivedB class FinalClass : public DerivedA, public DerivedB {}; int main(){ FinalClass obj; // 不管FinalClass如何组合其直接父母节点, // 都只会保留唯一的一份来自Base的基础属性。 obj.baseValue = 42; cout << "The value is: " << obj.baseValue << endl; return 0; } ``` 在这个示例里,无论 `FinalClass` 是怎样由 `DerivedA` 和 `DerivedB` 构建起来的,它最终都会维持单一版本的 `baseValue` 成员变量。 #### 实现细节分析 需要注意的是,虽然表面上看似乎只是简单加了个关键词而已,但实际上编译器背后做了很多额外工作来支持这一特性。比如调整构造顺序、增加指向虚表(vtable) 的指针等等。因此相较于普通的单向线性继承而言,性能开销会稍大一点;不过考虑到现代计算机硬件的能力以及由此获得的好处,这点代价通常是值得接受的[^4]。 另外值得注意的一个地方在于初始化列表上:对于虚基类来说,即使某个特定路径上的某级子类别已经对其进行了设定,最末端的实际创建实体仍然负有责任去显式指定参数给定值。这是因为只有后者才知道确切的需求是什么样的。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值