虚继承有什么作用

虚继承
 
   在标准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 ...

本文转自http://bbs.csdn.net/topics/100148304

时间: 2024-08-06 07:37:08

虚继承有什么作用的相关文章

C++ 虚函数和虚继承浅析

本文针对C++里的虚函数,虚继承表现和原理进行一些简单分析,有希望对大家学习C++有所帮助.下面都是以VC2008编译器对这两种机制内部实现为例. 虚函数 以下是百度百科对于虚函数的解释: 定义:在某基类中声明为 virtual 并在一个或多个派生类中被重新定 义的成员函数[1] 语法:virtual 函数返回类型 函数名(参数表) { 函数体 } 用途:实现多态性,通过指向派生类的基类指针,访问派生类中同名覆盖成员函数 函数声明和定义和普通的类成员函数一样,只是在返回值之前加入了关键字"vir

多重继承,虚继承,MI继承中虚继承中构造函数的调用情况

先来测试一些普通的多重继承.其实这个是显而易见的. 测试代码: [cpp] view plain copy print? //测试多重继承中派生类的构造函数的调用顺序何时调用 //Fedora20 gcc version=4.8.2 #include <iostream> using namespace std; class base { public: base() { cout<<"base created!"<<endl; } ~base()

虚函数列表: 取出方法 // 虚函数工作原理和(虚)继承类的内存占用大小计算 32位机器上 sizeof(void *) // 4byte

#include <iostream> using namespace std; class A { public: A(){} virtual void geta(){ cout << "A:A" <<endl; } virtual void getb(){ cout << "A:B" <<endl; } }; class B :public A{ public: B(){} virtual void g

c++ 虚继承

虚继承(个人感觉用到的地方不多,项目中没有用到这个的) 最典型的例子就是iostream的继承方式 class istream : virtual public ios{...};//此处就是虚继承,指定ios为虚基类 class ostream : virtual public ios{...}; class iostream public istream, public ostream{...}; 为了解决从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题,将共同基类设置

C++虚函数和虚继承

虚函数virtual表示动态继承,通过vptr指针指向vtable虚表,虚表中保存了每个虚函数的地址. 通过父类指针在运行时绑定子类方法. 虚继承:有几个父类,就有几个vtab和vptr 定义一个函数为纯虚函数,才代表函数没有被实现.定义他是为了实现一个接口,起到一个规范的作用,规范继承这个.类的程序员必须实现这个函数. virtual void fun() = 0;  // 说明fun函数为纯虚函数 两张图: http://blog.csdn.net/haoel/article/details

多重继承及虚继承中对象内存的分布

http://www.uml.org.cn/c++/201305163.asp 个人总结:多重继承时,对象中保存多个虚函数表指针, 虚拟继承时,对象中保存多个虚函数表指针,但被虚拟继承的基类对象在被继承的对象中只有一份,这个是靠共享其内容实现的.且共享的内容在子类对象的最后. 虚拟继承 为了避免上述Top类的多次继承,我们必须虚拟继承类Top. 1 class Top2 {3 public:4 int a;5 };67 class Left : virtual public Top8 {9 pu

[转载]多重继承及虚继承中对象内存的分布

粘过来的效果还不错:) 本位通过不断地完善讲解多重继承及虚继承中对象内存的分布. 读的时候不要着急,第一遍会有些晕,第二遍就会好很多. 能帮助加深对多重继承及虚继承对象内存的分布情况. 多重继承及虚继承中对象内存的分布     这篇文章主要讲解G++编译器中虚继承的对象内存分布问题,从中也引出了dynamic_cast和static_cast本质区别.虚函数表的格式等一些大部分C++程序员都似是而非的概念. 本文是介绍C++的技术文章,假定读者对于C++有比较深入的认识,同时也需要一些汇编知识.

C++基础6 【继承】 类型兼容 satatic 多继承 虚继承 【多态】 案例 虚析构函数 重载重写重定义

[继承] 继承的访问控制域 图 类型兼容性原则  指针 与 引用 用子类直接初始化父类 类的继承模型示意 图 [继承结论] [非常重要的概念] 继承与组合混搭情况下,构造和析构调用原则 原则:先构造父类,再构造成员变量.最后构造自己 先析构自己,在析构成员变量.最后析构父类 继承中,同名的成员变量的处理办法 继承中,同名的成员函数的处理办法 派生类中的static关键字 如果静态成员变量,你没有使用,也没有初始化的话 编译不会报错 经典错误 : 类中函数默认是private的,无法在外部访问 具

虚基类&amp;虚继承

发现这个月准备竞赛完全没有更新哎... 改了下某华大一c++测试题...网上对虚继承讲的要么太繁琐要么不到位,自力更生 1 #include<iostream> 2 #include<fstream> 3 using namespace std; 4 class A 5 { 6 public: 7 A(int i) { 8 a = i; 9 cout << "a=" << a << endl; 10 } 11 ~A() { c