C++ 虚基类

使用多继承,容易产生二义性。

1. 当派生类的多个直接基类中某个成员名称相同,派生类访问这个成员时,如果不指定基类名,编译器无法判断该成员来自哪一个基类。

class Base1{
	protected:
		int a;
	public:
		void set(int x):a(x){}
};
class Base2{
	protected:
		int a; //与Base1的成员a同名 
	public:
		void set(int x):a(x){}//与Base1的成员set(int x)同名
};

class MultiDeri : public Base1, public Base2{
	public:
		int get(){
			return a;// 二义性错误: Base1的a还是 Base2的a
		}
}; 

void main()
{
	MultiDeri md;
	md.set(30); // 二义性错误: Base1的set()还是 Base2的set()
}

解决办法:在被引用的基类成员前加上基类名和作用域限定符:: 

return Base1::a;

md.Base2.set(30);


2. 即使派生类的所有积累的成员都不同名,也可能出现一种很深的二义性错误。

根据类的继承原理,一个派生类包含了积累的所有成员,这些成员构成了所谓的基类对象。在多继承中,多层次、多路径的交叉派生关系可能导致派生类对象包含了某个基类子对象的多个同名副本。

普通基类

A            A    

↑            ↑

B           C

↖      ↗

      D

C++ 虚基类_第1张图片

在派生类 D的对象中存在基类A的成员(基类子对象)的两个副本:当试图访问基类A的成员时会发生二义性错误。

class A{
	protected:
		int a;
};
class B : public A{
	protected:
		int b;
};
class C : public A{
	protected:
		int c;
};
class D : public B, public C{
	public:
		int d;
}; 

void main()
{
	D md;//隐式调用基类构造函数 
	md.a = 100; // 二义性错误
}

d1含有基类A的成员a的两个副本(所谓的基类子对象),分别从路径D->B->A和D->C->A继承而来。因此编译器无法确定成员a是哪一个副本。

解决办法: md.B::a = 100; 


因此可以看出保存基类的多个成员的副本既浪费存储空间有可能造成二义性甚至多义性错误。派生类在访问间接基类的成员时,指明派生路径又很麻烦。所以C++提供了虚基类机制来解决。


虚基类

一种派生方式,在创建派生类的对象时,类层次结构中某个虚基类的子对象只保存一份,即一个派生类对象只有虚基类成员的一个副本:

解决办法:在被引用的基类成员前加上基类名和作用域限定符:: 

       A   

↗       ↖

B           C

↖      ↗

      D


C++ 虚基类_第2张图片

...
class B : public virtual A{
	protected:
		int b;
};
class C : public virtual A{
	protected:
		int c;
};
...
最后md.a = 100; 合法。


虚基类和一般基类的重要区别:

我们知道,一般基类的构造函数是由它的直接派生类的构造函数负责调用的,直接派生类创建对象时调用直接基类的构造函数。但虚基类则不同。

原因是虚基类子对象在内存中只有一份,说明构造函数只能执行一次。为了保证虚基类的构造函数只被调用一次,C++规定徐基类的构造函数是由最派生类(创建对象的类为最派生类)的构造函数调用的。而该最派生类的非虚基类对虚基类构造函数的调用将被忽略。

同样的,虚基类有默认构造函数的话,隐式调用就是虚基类和它的派生类的构造函数自动调用默认构造函数。如果虚基类没有默认构造函数,则需要显示方式调用徐基类的构造函数。最派生类显式调用徐基类的构造函数时,该派生类的非虚基类对虚基类的构造函数的调用都被C++编译器忽略,这样保证对虚基类的子对象只初始化一次。也就是,显式调用也只调用最派生类对虚基类的调用。

其他的,如调用顺序问题是按照定义派生类时基类声明顺序。

你可能感兴趣的:(C++开发)