C++多重继承与虚继承分析

本文以实例形式较为全面的讲述了C++的多重继承与虚继承,是大家深入学习C++面向对象程序设计所必须要掌握的知识点,具体内容如下:

一、多重继承

我们知道,在单继承中,派生类的对象中包含了基类部分 和 派生类自定义部分。同样的,在多重继承(multiple inheritance)关系中,派生类的对象包含了每个基类的子对象和自定义成员的子对象。下面是一个多重继承关系图:

class A{ /* */ };
class B{ /* */ };
class C : public A { /* */ };
class D : public B, public C { /* */ };

C继承了A,派生类D又继承了B和C,如图所示,一个D对象中含有一个B部分、一个C部分(其中又含有一个A部分)以及在D中声明的非静态数据成员:

构造与析构:

构造一个派生类对象将首先构造它的所有基类子对象,其中基类的构造顺序与派生列表中基类的出现顺序保持一致,即B –> A –> C –> D。
销毁一个派生类对象的顺序正好与其创建的顺序相反,即析构函数的调用顺序正好与构造函数相反,即D –> C –> A –> B。注意派生类的析构函数只负责清除派生类本身分配的资源(析构函数体),派生类的成员及基类都是自动销毁的(隐式析构阶段)。

类型转换:

在多重继承的情况下,可以令某个可访问基类的指针或引用直接指向一个派生类对象。编译器不会在派生类向基类的几种转换中进行比较和选择,在它看来转换到任意一种基类都一样好。

二、虚继承

尽管在派生列表中不允许同一个基类出现两次,但实际上派生类可以多次继承同一个类。

派生类通常会含有继承链上每个类对应的子部分。在上面的两种情况中,class D都间接地继承了class A两次,那么意味着class D中包含了class A的两份拷贝。所以在一个class D的对象中将含有2组class A的成员,此时若不加前缀限定符直接使用某个成员将引发“二义性”错误:

class A{
public:
  A():str("name"){};
  string str;
  void print(){cout << str << endl;};
}; 

class B : public A { };
class C : public A { };
class D : public B, public C { }; 

int main(){
  D d;
  d.str = "songlee"; // 错误:对成员‘str'的请求有歧义
  d.print(); // 错误:对成员‘print'的请求有歧义
  return 0;
}

当然你可以使用作用域 d.B::str="songlee"; 和 d.B::print(); 来规避“二义性”错误,但这并没有从根本上解决问题。

为了解决上述问题,C++提供了虚继承(virtual inheritance)的机制。虚继承的目的是令某个类作出声明,承诺愿意共享它的基类。其中,共享的基类子对象称为虚基类。在这种机制下,不论虚基类在继承体系中出现多少次,在派生类中都只包含唯一一个共享的虚基类子对象。我们指定虚基类的方式是在派生列表中添加关键字virtual:

class A{
public:
  A():str("name"){};
  string str;
  void print(){cout << str << endl;};
}; 

class B : virtual public A { };  // 虚继承,A为虚基类
class C : virtual public A { };  // 关键字public和virtual的顺序随意
class D : public B, public C { }; 

int main(){
  D d;
  d.str = "songlee";  // 正确
  d.print();      // 正确
  return 0;
}

通过在派生列表中添加virtual(关键字public和virtual的顺序随意)指定A为虚基类,B和C将共享A的同一份实例,这样在D的对象中也将只有A的唯一一份实例,所以A的成员可以被直接访问,并且不会产生二义性。

虚继承最典型的应用是iostream继承于istream和ostream,而istream和ostream虚继承于ios:

class istream : virtual public ios { /* */ };
class ostream : virtual public ios { /* */ };
class iostream : public istream, public ostream { /* */ };

此外还需要注意:

1.支持向基类的常规类型转换。也就是说即使基类是虚基类,也能通过基类的指针或引用操作派生类的对象。

2.虚继承只是解决了一个派生类对象中存在同一个基类的多份拷贝的问题,并没有解决多个基类存在同名成员的二义性问题。

3.在虚继承中,虚基类是由最低层的派生类负责初始化的。如上例中,当创建一个D对象时,D位于派生的最低层并由它负责初始化共享的A基类部分。

4.含有虚基类的对象的构造顺序与一般的多重继承的构造顺序稍有区别:先初始化虚基类子对象(最低层派生类负责),然后按派生列表中的顺序依次对直接基类(非虚)进行初始化。

5.析构的顺序与构造的顺序正好相反。

(0)

相关推荐

  • C++继承介绍

    然后是各个成员函数选项可以是virtual或non-virtual或pure virtual.本文仅仅作出一些关键点的验证. public继承,例如下: 复制代码 代码如下: class base {...} class derived:public base {...} 如果这样写,编译器会理解成类型为derived的对象同时也是类型为base的对象,但类型为base的对象不是类型为derived的对象.这点很重要.那么函数形参为base类型适用于derived,形参为derived不适用于b

  • C++多继承同名隐藏实例详细介绍

    如果某个派生类的部分或者全部直接基类是从另一个共同的基类派生而来,在这些俄直接基类中, 从上一级基类继承来的成员就拥有相同的名称,因此派生类中就会出现同名现象.对这种类型的同名成员也要使用作用域分辨符来唯一标识,而且必须使用直接基类来进行限定. -------------------------------------------------- /* * File: main.cpp * Author: yubao * * Created on May 31, 2009, 8:54 AM */

  • 深入解析C++中类的多重继承

    C++类的多继承 在前面的例子中,派生类都只有一个基类,称为单继承.除此之外,C++也支持多继承,即一个派生类可以有两个或多个基类. 多继承容易让代码逻辑复杂.思路混乱,一直备受争议,中小型项目中较少使用,后来的 Java.C#.PHP 等干脆取消了多继承.想快速学习C++的读者可以不必细读. 多继承的语法也很简单,将多个基类用逗号隔开即可.例如已声明了类A.类B和类C,那么可以这样来声明派生类D: class D: public A, private B, protected C{ //类D新

  • C++ 多重继承和虚拟继承对象模型、效率分析

    一.多态 C++多态通过继承和动态绑定实现.继承是一种代码或者功能的传承共享,从语言的角度它是外在的.形式上的,极易理解.而动态绑定则是从语言的底层实现保证了多态的发生--在运行期根据基类指针或者引用指向的真实对象类型确定调用的虚函数功能!通过带有虚函数的单一继承我们可以清楚的理解继承的概念.对象模型的分布机制以及动态绑定的发生,即可以完全彻底地理解多态的思想.为了支持多态,语言实现必须在时间和空间上付出额外的代价(毕竟没有免费的晚餐,更何况编译器是毫无感情): 1.类实现时增加了virtual

  • C++中virtual继承的深入理解

    今天专门看了一下虚继承的东西,以前都没怎么用过,具体如下:父类:  复制代码 代码如下: class   CParent { .... }; 继承类的声明比较特别: class   CChild   :   virtual   public   CParent { .... } 请问,这个"virtual"是什么作用及含义? --------------------------------------------------------------- 表示虚拟继承,和普通继承是C++的

  • C++继承中的访问控制实例分析

    本文较为深入的探讨了C++继承中的访问控制,对深入掌握C++面向对象程序设计是非常必要的.具体内容如下: 通常来说,我们认为一个类有两种不同的用户:普通用户 和 类的实现者.其中,普通用户编写的代码使用类的对象,这部分代码只能访问类的公有(接口)成员:实现者则负责编写类的成员和友元的代码,成员和友元既能访问类的公有部分,也能访问类的私有部分.如果进一步考虑继承的话就会出现第三种用户,即派生类.派生类可以访问基类的公有(public)成员和受保护(protected)成员,但不能访问基类的私有(p

  • 详解C++中实现继承string类的MyString类的步骤

    昨天师兄又出了道测试题,让我们实现类似于string类的没有MyString类,刚开始很头疼,可是真正在自己写代码的时候又很兴奋的发现,这个过程真的是个很宝贵的机会,让我又有机会可以很好的熟悉回顾C++的很多知识-类设计,构造析构函数,成员函数,友元函数,引用,重载,字符串操作,动态内存分布.....于是昨天花了半天时间写了300多行代码,并认真的进行了相关测试.修改和总结.因为内容有点丰富,所以想分几次写出来,条理也清楚些. 类的空间分配:类给它的每个对象都分配了独立的空间去存储它的数据成员,

  • C++ 中继承与动态内存分配的详解

    C++ 中继承与动态内存分配的详解 继承是怎样与动态内存分配进行互动的呢?例如,如果基类使用动态内存分配,并重新定义赋值和复制构造函数,这将怎样影响派生类的实现呢?这个问题的答案取决于派生类的属性.如果派生类也使用动态内存分配,那么就需要学习几个新的小技巧.下面来看看这两种情况: 一.派生类不使用new 派生类是否需要为显示定义析构函数,复制构造函数和赋值操作符呢? 不需要! 首先,来看是否需要析构函数,如果没有定义析构函数,编译器将定义一个不执行任何操作的默认构造函数.实际上,派生类的默认构造

  • C++多重继承与虚继承分析

    本文以实例形式较为全面的讲述了C++的多重继承与虚继承,是大家深入学习C++面向对象程序设计所必须要掌握的知识点,具体内容如下: 一.多重继承 我们知道,在单继承中,派生类的对象中包含了基类部分 和 派生类自定义部分.同样的,在多重继承(multiple inheritance)关系中,派生类的对象包含了每个基类的子对象和自定义成员的子对象.下面是一个多重继承关系图: class A{ /* */ }; class B{ /* */ }; class C : public A { /* */ }

  • 关于C++中菱形继承和虚继承的问题总结

    前言 菱形继承是多重继承中跑不掉的,Java拿掉了多重继承,辅之以接口.C++中虽然没有明确说明接口这种东西,但是只有纯虚函数的类可以看作Java中的接口.在多重继承中建议使用"接口",来避免多重继承中可能出现的各种问题.本文将给大家详细介绍关于C++菱形继承和虚继承的相关内容,分享出来供大家参考学习,话不多说了,来一起看看详细的介绍吧. 继承: 1. 单继承–一个子类只有一个直接父类时称这个继承关系为单继承 2. 多继承–一个子类有两个或以上直接父类时称这个继承关系为多继承 例如下面

  • c++ 虚继承,多继承相关总结

    看这一篇文章之前强烈建议先看以下我之前发布的 虚指针,虚函数剖析 例1: 以下代码输出什么? #include <iostream> using namespace std; class A { protected: int m_data; public: A(int data = 0) {m_data=data;} int GetData() { return doGetData(); } virtual int doGetData() { return m_data; } }; class

  • 关于C++虚继承的内存模型问题

    1.前言 C++虚继承的内存模型是一个经典的问题,其具体实现依赖于编译器,可能会出现较大差异,但原理和最终的目的是大体相同的.本文将对g++中虚继承的内存模型进行详细解析. 2.多继承存在的问题 C++的多继承是指从多个直接基类中产生派生类的能力,多继承的派生类继承了所有父类的成员.从概念上来讲这是非常简单的,但是多个基类的相互交织可能会带来错综复杂的设计问题,命名冲突就是不可回避的一个,比如典型的是菱形继承,如图2-1所示: 图2-1 菱形继承 在图2-1中,类A派生出类B和类C,类D继承自类

  • C++ 继承,虚继承(内存结构)详解

    目录 普通的公有继承 多重继承 虚继承 虚继承(菱形继承) 总结 普通的公有继承 class test1 { public: test1(int i) :num1(i) {} private: int num1; }; class test2 : public test1 { public: test2(int i,int j) : test1(i), num2(j) { } private: int num2; }; void main() { test2 t2(1,2); } (test2内

  • c++基础语法:虚继承

    虚继承 的概念的提出主要是为了解决C++多继承的问题,举个最简单的例子: 复制代码 代码如下: class animal{        public :              void op()                  {cout << "hello animal" ;} };class tiger : public animal {        public :              void tg()                  {cout

  • Python多重继承之菱形继承的实例详解

    继承是面向对象编程的一个重要的方式,通过继承,子类就可以扩展父类的功能.在python中一个类能继承自不止一个父类,这叫做python的多重继承(Multiple Inheritance ). 语法 class SubclassName(BaseClass1, BaseClass2, BaseClass3, ...): pass 菱形继承 在多层继承和多继承同时使用的情况下,就会出现复杂的继承关系,多重多继承. 其中,就会出现菱形继承.如下图所示.mark 在这种结构中,在调用顺序上就出现了疑惑

  • C++详细讲解继承与虚继承实现

    目录 继承的概念及定义 概念: 定义: 继承关系和访问限定符 总结 基类和派生类对象赋值转换 继承中的作用域 派生类的默认成员函数 继承与友元 继承与静态成员 复杂的菱形继承及菱形虚拟继承 虚继承原理 继承的总结 继承的概念及定义 概念: 继承机制是面向对象程序设计为了提高代码复用率的一种手段,它可以保持原类特性的基础上进行拓展,简单来说继承是类层次的复用. 接下来我们来看一个简单的继承 class Person { public: void Print() { cout<<"nam

  • C++虚继承的实现原理由内存布局开始讲起

    目录 准备工作 虚继承的内存分布情况 准备工作 1.VS2012使用命令行选项查看对象的内存布局 微软的Visual Studio提供给用户显示C++对象在内存中的布局的选项:/d1reportSingleClassLayout.使用方法很简单,直接在[工具(T)]选项下找到“Visual Studio命令提示(C)”后点击即可.切换到cpp文件所在目录下输入如下的命令即可 c1 [filename].cpp /d1reportSingleClassLayout[className] 其中[fi

随机推荐