出现二义性的原因: 派生类在访问基类成员函数时,由于基类存在同名的成员函数,导致无法确定访问的是哪个基类的成员函数,因此出现了二义性错误。
1. 什么是多重继承的二义性
class A{
public:
void f();
}
class B{
public:
void f();
void g();
}
class C:public A,public B{
public:
void g();
void h();
};
如果声明:C c1,则c1.f();具有二义性,而c1.g();无二义性(同名覆盖)。
2. 解决办法一 -- 类名限定
调用时指名调用的是哪个类的函数,如
c1.A::f();
c1.B::f();
3. 解决办法二 -- 同名覆盖
在C中声明一个同名函数,该函数根据需要内部调用A的f或者是B的f。如
class C:public A,public B{
public:
void g();
void h();
void f(){
A::f();
}
};
4. 解决办法三 -- 虚基类(用于有共同基类的场合)
virtual 修饰说明基类,如:
class B1:virtual public B
虚基类主要用来解决多继承时,可能对同一基类继承继承多次从而产生的二义性。为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝。注意:需要在第一次继承时就要将共同的基类设计为虚基类。虚基类及其派生类构造函数建立对象时所指定的类称为最(远)派生类。
- 虚基类的成员是由派生类的构造函数通过调用虚基类的构造函数进行初始化的。
- 在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中给出对虚基类的构造函数的调用。如果未列出,则表示调用该虚基类的缺省构造函数。
- 在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,该派生类的其他基类对虚基类的构造函数的调用被忽略。
class B{ public: int b; } class B1:virtual public B{ priavte: int b1; } class B2:virutual public B{ private: int b2; } class C:public B1,public B2{ private: float d; } C obj; obj.b;//正确的
如果B1和B2不采用虚基类,则编译出错,提示“request for member 'b' is ambiguous”。这是因为,不指名virtual的继承,子类将父类的成员都复制到自己的空间中,所以,C中会有两个b。
下面我们来看两个例子:
#include<iostream>
using namespace std;
class B0{
public:
B0(int n) {
nv=n;
cout<<"i am B0,my num is"<<nv<<endl;
}
void fun() {
cout<<"Member of Bo"<<endl;
}
private:
int nv;
};
class B1:virtual public B0{
public:
B1(int x,int y):B0(y){
nv1=x;
cout<<"i am B1,my num is "<<nv1<<endl;
}
private:
int nv1;
};
class B2:virtual public B0{
public:
B2(int x,int y):B0(y){
nv2=x;
cout<<"i am B2,my num is "<<nv2<<endl;
}
private:
int nv2;
};
class D:public B1,public B2{
public:
D(int x,int y,int z,int k):B0(x),B1(y,y),B2(z,y){
nvd=k;
cout<<"i am D,my num is "<<nvd<<endl;
}
private:
int nvd;
};
int main(){
D d(1,2,3,4);
d.fun();
return 0;
}
结果:
i am B0,my num is 1
i am B1,my num is 2
i am B2,my num is 3
i am D,my num is 4
Member of Bo
/*多继承的二义性*/
#include <iostream>
using namespace std;
class Base1{ //定义基类Base1
public:
int var;
void fun(){
cout << "Member of Base1" << endl;
}
};
class Base2{
public:
int var;
void fun(){
cout << "Member of Base2 " << endl;
}
};
class Derived: public Base1, public Base2{ //定义派生类Derived
public:
int var; //同名数据成员
void fun(){ //同名函数成员
cout << "Member of Derived" << endl;
}
};
int main(){
Derived d;
Derived *p = &d;
d.var = 1; //访问Derived类成员
d.fun();
d.Base1::var = 2; //作用域分别标识符,避免二义性,访问基类中同名的数据成员
d.Base1::fun();
p->Base2::var = 3; //作用域分别标识符,避免二义性,访问基类中同名的数据成员
p->Base2::fun();
return 0;
}
下面我们再看一个使用虚基类解决二义性的问题的例子:
/*虚基类*/
#include <iostream>
using namespace std;
class Base0{ //定义基类Base0
public:
int var0;
void fun0(){
cout << "Member of Base0" << endl;
}
};
class Base1: virtual public Base0{ //定义派生类Base1,第一级继承时就要将共同的基类设计为虚基类
public:
int var1;
void fun(){
cout << "Member of Base1" << endl;
}
};
class Base2: virtual public Base0{ //定义派生类Base2,第一级继承时就要将共同的基类设计为虚基类
public:
int var2;
void fun(){
cout << "Member of Base2 " << endl;
}
};
class Derived: public Base1, public Base2{//定义派生类Derived,因为Derived多继承而来的Base1,Base2,他们同时共同继承Base0,
public: //所以空间里会产生两个var0和fun0(),对象调用他们时,会有二义性,但如果在他们继承Base0时使用关键字virtual就可以消除影响
int var; //同名数据成员
void fun(){ //同名函数成员
cout << "Member of Derived" << endl;
}
};
int main(){
Derived d;
d.var0 = 2;//直接访问虚基类的数据成员
d.fun0();
return 0;
}
/*虚基类及其派生类的构造函数*/
#include <iostream>
using namespace std;
class Base0{ //定义基类Base0
public:
Base0(int var): var0(var){} //构造函数
int var0;
void fun0(){
cout << "Member of Base0" << endl;
}
};
class Base1: virtual public Base0{ //定义派生类Base1,第一级继承时就要将共同的基类设计为虚基类
public:
Base1(int var): Base0(var){}//派生类的构造函数向基类构造函数传参
int var1;
void fun(){
cout << "Member of Base1" << endl;
}
};
class Base2: virtual public Base0{ //定义派生类Base2,第一级继承时就要将共同的基类设计为虚基类
public:
Base2(int var): Base0(var){}
int var2;
void fun(){
cout << "Member of Base2 " << endl;
}
};
class Derived: public Base1, public Base2{//定义派生类Derived,因为Derived多继承而来的Base1,Base2,他们同时共同继承Base0,
public: //所以空间里会产生两个var0和fun0(),对象调用他们时,会有二义性,但如果在他们继承Base0时使用关键字virtual就可以消除影响
Derived(int var): Base0(var), Base1(var), Base2(var){}
int var; //同名数据成员
void fun(){ //同名函数成员
cout << "Member of Derived" << endl;
}
};
int main(){
Derived d(1);//Derived是最远派生类,它的构造函数会调用虚基类的构造函数,而Base1和Base2对虚基类的构造函数的调用都会被自动忽略 ,并且Base1和Base2也不会被初始化,只有Base0(1)初始化为1;
d.var = 2;//直接访问虚基类的数据成员
cout << d.var0 << endl << d.var1 << endl << d.var2 << endl;
d.fun();
return 0;
}
由运行结果可以看的出最远派生类Derived的构造函数只对虚基类Base0进行了初始化,但Base1和Base2并没有初始化,依旧存放的垃圾数据。