C++ Primer学习笔记——$19 多重继承与虚继承

转载 2011年01月19日 15:13:00

题记:本系列学习笔记(C++ Primer学习笔记)主要目的是讨论一些容易被大家忽略或者容易形成错误认识的内容。只适合于有了一定的C++基础的读者(至少学完一本C++教程)。

 
作者: tyc611, 2007-03-02
   本文主要讨论C++的多重继承与虚继承机制。
   如果文中有错误或遗漏之处,敬请指出,谢谢!
多重继承
 
   在多重继承中,基类的构造函数的调用次序既不受派生类构造函数初始化列表中出现的基类构造函数的影响,也不受基类在构造函数初始化列表中的出现次序的影响,它按照基类在类派生列表中的出现次序依次调用相应的基类构造函数。析构顺序与构造顺序逆序进行。
 
   多重继承中,派生类的指针或引用可以转换为其任意基类的指针或引用。因此,这种转换更可能遇到二义性问题。
 
   在多重继承中,成员函数中使用的名字的查找首先在函数本身进行,如果不能在本地找到名字,就继续在成员的类中查找,然后同时(并行)查找所有基类继承子树。多重继承的派生类有可能从两个或多个基类继承同名成员,对该成员不加限定的使用是二义性的。
 
   注意,多重继承中首先发生名字查找。你可能会感到吃惊的是,即使两个继承的同名函数有不同的形参表,也会产生错误。类似地,即使函数在一个类中是私有的而在另一个类中是公有或者受保护的,也是错误的。或者,在一个类给定义了函数,而在另一个类中没有定义,调用仍是错误的。例如:
 
#include <iostream>

class Base1 {
public:
    void print() {}
    void display() {}
    void show() {}
};

class Base2 {
public:
    void print(int) {}
    void show(int);
private:
    void display(int) {}
};

class Derived: public Base1, public Base2 {
};

int main () {
    Derived d;
    d.print();
    d.display();
    d.show();
    
    return 0;
}

编译结果(MinGW2.05):
Compiling source file(s)...
main.cpp
main.cpp: In function `int main()':
main.cpp:24: error: request for member `print' is ambiguous
main.cpp:13: error: candidates are: void Base2::print(int)
main.cpp:6: error:                 void Base1::print()
main.cpp:25: error: request for member `display' is ambiguous
main.cpp:16: error: candidates are: void Base2::display(int)
main.cpp:7: error:                 void Base1::display()
main.cpp:26: error: request for member `show' is ambiguous
main.cpp:14: error: candidates are: void Base2::show(int)
main.cpp:8: error:                 void Base1::show()

Test.exe - 9 error(s), 0 warning(s)

 
   解决这种二义性的方法可以是通过指定使用哪个类的版本(即带上类名前缀)来解决。但最好的方法是,在解决二义性的派生类中定义函数的一个版本。
 
虚继承
 
   在标准I/O库中的类都继承了一个共同的抽象基类ios,那个抽象基类管理流的条件状态并保存流所读写的缓冲区。istream和ostream类直接继承这个公共基类,库定义了另一个名为isotream的类,它同时继承istream和ostream,iostream类既可以对流进行读又可以对流进行写。如果I/O类型使用常规继承,则每个iostream对象可能包含两个ios子对象:一个包含在它的istream子对象中,另一个包含在它的ostream子对象中。从设计角度讲,这个实现是错误的:iostream类想要对单个缓冲区进行读和写,它希望跨越输入和输出操作符共享条件状态。
 
   在C++中,通过使用虚继承(virtual inheritance)解决这类问题。虚继承是一种机制,类通过虚继承指出它希望共享其虚基类的状态。在虚继承下,对给定虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象。共享的基类子对象称为虚基类(virtual base class)
 
   通过在派生类列表中包含关键字virtual设置虚基类,例如:
   class istream : public virtual ios {...};
   class ostream : virtual public ios {...};
   class iostream : public istream, public ostream {...};
 
   假定通过多个派生路径继承名为X的成员,有下面三种可能性:
   1)如果在每个路径中X表示同一虚基类成员,则没有二义性,因为共享该成员的单个实例;
   2)如果在某个路径中X是虚基类的成员,而在另一路径中X是后代派生类的成员,也没有二义性——特定派生类实例的优先级高于共享虚基类实例。
   3)如果沿每个继承路径X表示后代派生类的不同成员,则该成员的直接访问是二义性的。
 
   例如:

#include <iostream>

class B {
public:
    void print() {
        std::cout << "B" << std::endl;
    }
};

class D1: public virtual B {
};

class D2: public virtual B {
public:
    void print() {
        std::cout << "D2" << std::endl;
    }
};

class DD: public D1, public D2 {
};

int main () {
    DD d;
    d.print();    // ok: call D2::print
    
    return 0;
}

 
特殊的初始化语义
 
   通常,每个类只初始化自己的直接基类。在应用于虚基类的时候,这个初始化策略会失败。如果使用常规规则,就可能会多次初始化虚基类。类将沿着包含该虚基类的每个继承路径初始化。
 
   为了解决这个重复初始化问题,从具有虚基类的类继承的类对初始化进行特殊处理。在虚派生中,由最低层派生类的构造函数初始化虚基类。
 
   虽然由最低层派生类初始化虚基类,但是任何直接或间接继承虚基类的类一般也必须为该基类提供自己的初始化式。只要可以创建虚基类派生类类型的独立对象,该类就必须初始化自己的虚基类,这些初始化式只在创建中间类型的对象时使用。
 
   例如,我们有四个类:ZooAnimal, Bear, Raccoon和Panda,它们之间构造一个继承层次:Bear和Raccoon继承ZooAnimal,Panda继承Bear和Raccoon。那么,它们的构造函数就形如:
   Bear::Bear(std::string name, bool onExhibit): ZooAnimal(name, onExhibit, "Bear") {}
   Raccoon::Raccoon(std::string name, bool onExhibit): ZooAnimal(name, onExhibit, "Raccoon") {}
   Panda::Panda(std::string name, bool onExhibit): ZooAnimal(name, onExhibit, "Panda"), Bear(name, onExhibit), Raccoon(name, onExhibit) {}
 
   当创建Panda对象的时候,构造过程如下:
   1)首先使用构造函数初始化列表中指定的初始化式构造ZooAnimal部分;
   2)接下来,构造Bear部分(在派生列表中Bear在前)。忽略Bear初始化列表中用于ZooAnimal构造函数的初始化式;
   3)然后,构造Raccoon部分,再次忽略ZooAnimal初始化式;
   4)最后,构造Panda部分。
 
   如果Panda构造函数不显式初始化ZooAnimal基类,就使用ZooAnimal默认构造函数;如果ZooAnimal没有默认构造函数,则代码出错。
 
   无论虚基类出现在继承层次中的任何地方,总是在构造非虚基类之前构造虚基类。
 
   例如,有下面的继承关系:
   class Character { };
   class BookCharater: public Character { };
   class ToyAnimal { };
   class TeddyBear: public BookCharacter, public Bear, public virtual ToyAnimal { };
 
直观继承图为:
   按声明次序检查直接基类,确定是否存在虚基类。上例中,首先检查BookCharacter的继承子树,然后检查Bear的继承子树,最后检查ToyAnimal的继承子树。按从根类开始向下到最低层派生类的次序检查每个子树。在这里依次检查到ZooAnimal和ToyAnimal为虚基类。
 
   TeddyBear的虚基类的构造次序是先ZooAnimal再ToyAnimal(检查到的顺序)。一旦构造了虚基类,就按声明次序调用非虚基类的构造函数:首先是BookCharacter,它导致调用Character构造函数,然后是Bear。在这里,由最低层派生类TeddyBear指定用于ZooAnimal和ToyAnimal的初始化式。
 
   当然,对于析构函数的调用顺序与构造函数相反。
 
   示例代码如下:
 

#include <iostream>

class Character {
public:
    Character() {
        std::cout << "Character Constructor" << std::endl;
    }
    ~Character() {
        std::cout << "Character Destructor" << std::endl;
    }
};

class BookCharacter: public Character {
public:
    BookCharacter() {
        std::cout << "BookCharacter Constructor" << std::endl;
    }
    ~BookCharacter() {
        std::cout << "BookCharacter Destructor" << std::endl;
    }
};

class ZooAnimal {
public:
    ZooAnimal() {
        std::cout << "ZooAnimal Constructor" << std::endl;
    }
    ~ZooAnimal() {
        std::cout << "ZooAnimal Destructor" << std::endl;
    }
};

class Bear: public virtual ZooAnimal {
public:
    Bear() {
        std::cout << "Bear Constructor" << std::endl;
    }
    ~Bear() {
        std::cout << "Bear Destructor" << std::endl;
    }
};

class ToyAnimal {
public:
    ToyAnimal() {
        std::cout << "ToyAnimal Constructor" << std::endl;
    }
    ~ToyAnimal() {
        std::cout << "ToyAnimal Destructor" << std::endl;
    }
};


class TeddyBear: public BookCharacter, public Bear, public virtual ToyAnimal {
public:
    TeddyBear() {
        std::cout << "TeddyBear Constructor" << std::endl;
    }
    ~TeddyBear() {
        std::cout << "TeddyBear Destructor" << std::endl;
    }
};

int main () {
    TeddyBear tb;
    
    return 0;
}

运行结果如下:

ZooAnimal Constructor
ToyAnimal Constructor
Character Constructor
BookCharacter Constructor
Bear Constructor
TeddyBear Constructor
TeddyBear Destructor
Bear Destructor
BookCharacter Destructor
Character Destructor
ToyAnimal Destructor
ZooAnimal Destructor


Terminated with return code 0
Press any key to continue ...

 

   如果文中有错误或遗漏之处,敬请指出,谢谢!

参考文献:
[1] C++ Primer(Edition 4)
[2] Thinking in C++(Volume Two, Edition 2)
[3] International Standard:ISO/IEC 14882:1998

C++ Primer笔记(十七)多重继承与虚继承

多重继承是从多于一个直接基类派生类的能力。多重继承的派生类继承其所有父类的属性。 为了支持多重继承,应扩充派生列表,多个类用逗号分割。如: class panda:public bear,publ...
  • woshibendangao
  • woshibendangao
  • 2014年04月27日 11:11
  • 1003

C++ Primer学习笔记——$19 多重继承与虚继承 (

题记:本系列学习笔记(C++ Primer学习笔记)主要目的是讨论一些容易被大家忽略或者容易形成错误认识的内容。只适合于有了一定的C++基础的读者(至少学完一本C++教程)。   作者: ty...
  • lovenankai
  • lovenankai
  • 2011年10月08日 11:34
  • 347

C++ Primer学习笔记——$19 多重继承与虚继承

多重继承    在多重继承中,基类的构造函数的调用次序既不受派生类构造函数初始化列表中出现的基类构造函数的影响,也不受基类在构造函数初始化列表中的出现次序的影响,它按照基类在类派生列表中的出现次序依次...
  • minkowsky
  • minkowsky
  • 2010年04月19日 05:10
  • 642

c++学习之继承篇(多重继承之虚继承)

虚继承 是面向对象编程中的一种技术,是指一个指定的基类,在继承体系结构中,将其成员数据实例共享给也从这个基类型直接或间接派生的其它类。形式:在继承定义中包含了virtual关键字的继承关系,如下图中,...
  • hudfang
  • hudfang
  • 2016年01月25日 12:09
  • 900

<C++略识>之多重继承、多继承、虚继承

问题:什么是多重继承? 定义三个类:人、士兵、步兵,则有:步兵士兵人,这样的关系称之为多重继承,写法如下:class Person { }; classSoldier:publicPerson ...
  • u013003827
  • u013003827
  • 2016年07月22日 22:29
  • 3375

C++ 深入理解 虚继承、多重继承和直接继承

本文从5段代码实例出发,通过类中类的普通继承,类的虚继承,类的多重继承,多个虚函数类的普通继承、虚继承与多重继承,几个交叉概念,详细的阐释了继承、虚函数与虚继承的基本概念,深入剖析了继承于虚继承的区别...
  • u013630349
  • u013630349
  • 2015年07月25日 16:54
  • 6982

C++ Primer学习笔记 多重继承与虚继承

http://www.cublog.cn/u/18517/showart_252162.html 题记:本系列学习笔记(C++ Primer学习笔记)主要目的是讨论一些容易被大家忽略或者容易形成...
  • kannju
  • kannju
  • 2011年09月01日 14:51
  • 366

c++ Primer学习笔记

第一章 文件头及声明 关于extern 使用extern 声明而不定义,它是说明变量定义在程序其他地方   全局不初始化的extern int i; 是声明不定义;只要声明并且有初始化式,那么...
  • leiming32
  • leiming32
  • 2012年12月17日 23:57
  • 5216

C++ 虚函数 单一继承 多重继承 单一虚继承 菱形虚继承 的简洁总结

虚函数所谓虚函数,从其功能上来看是采用虚调用的方式;当子类重新定义其父类的虚函数以后,父类指针根据付给他的不同子类指针,动态地调用属于子类的该函数;假如一个类有虚函数,则该类会因为维护该虚函数,而额外...
  • misol
  • misol
  • 2011年07月13日 21:07
  • 3183

关于多重继承和虚拟继承

这两个概念在C++的书里面似乎也很少提及,所以看到这两个名词的时候我也往往自动忽略,今天有时间上网百度了一下,找到三篇写得不错的文章: 《详解多重继承》 《关于C++中的虚拟继承的一些总结》 《详解C...
  • huang1433
  • huang1433
  • 2015年11月02日 14:45
  • 408
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C++ Primer学习笔记——$19 多重继承与虚继承
举报原因:
原因补充:

(最多只允许输入30个字)