9:下列公共基类导致的二义性如何解决?

class A{                            //公共基类

public:                             //public 成员列表

void print(){

cout << "this is x in A: " <<endl;}

class B: public A{};

class C: public A{};

class D : public B, public C{};

void main(){

D d;                        //声明一个D类对象d

A* pa=(A*) &d;       //上行转换产生二义性

d.print () ;   //print ()具有二义性,系统不知道是调用B类的还是C类的print ()函数

}

注意:把子类的指针或引用转换成基类指针或引用是上行转换,把基类指针或引用转换成子类指针或引用是下行转换

解答:1) main函数中语句“d.print();”编译错误,可改为以下的一种:

d.B::print(); d.C::print();

若改为“d.A::print();”又会如何呢?

由于d对象中有两个A类对象,故编译会报“基类A不明确”。

2)   语句“A*pa=(A*)&d;”产生的二义性是由于d中含有两个基类对象A(下面路径二义性示例和图解很重要),隐式 转换时不知道让 pa指向哪个子对象,从而出错。可改为以下的一种:

A*pa=(A*)(B*)&d; //上行转换

A*pa=(A*)(C*)&d; //上行转换

事实上,使用关键字virtual将共同基类A声明为虚基类,可有效解决上述  问题。

 

二义性问题

1.在继承时,基类之间、或基类与派生类之间发生成员同名时,将出现对成员访问的不确定性——同名二义性。

2.当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生另一种不确定性——路径二义性。

 

同名二义性

同名隐藏规则——解决同名二义的方法

             当派生类与基类有同名成员时,派生类中的成员将屏蔽基类中的同名成员。

             若未特别指明,则通过派生类对象使用的都是派生类中的同名成员;

             如要通过派生类对象访问基类中被屏蔽的同名成员,应使用基类名限定(::)。

多继承同名隐藏举例

 

[cpp] view plain copy
 
  1. //多继承同名隐藏举例  
  2. #include <iostream>  
  3. using namespace std;  
  4. class B1//声明基类B1  
  5. {   
  6. public:  
  7.     int nV;  
  8.     void fun() {cout<<"Member of B1"<<endl;}  
  9. };  
  10. class B2//声明基类B2  
  11. {   
  12. public:  
  13.     int nV;  
  14.     void fun() {cout<<"Member of B2"<<endl;}  
  15. };  
  16. class D1: public B1, public B2  
  17. {   
  18. public:  
  19.     int nV;//同名数据成员  
  20.     void fun(){cout<<"Member of D1"<<endl;} //同名函数成员  
  21. };  
  22.   
  23.   
  24. void main()  
  25. {  
  26.     D1 d1;  
  27.     //用“对象名.成员名”访问子类成员。  
  28.     d1.nV=1;  
  29.     d1.fun();  
  30.     //加“作用域分辨符标识”, 可访问基类被屏蔽的成员  
  31.     d1.B1::nV=2;  
  32.     d1.B1::fun();  
  33.     d1.B2::nV=3;  
  34.     d1.B2::fun();  
  35. }  

 

 

 

同名二义性的解决方法

解决方法一:用类名来限定c1.A::f() 或c1.B::f()

解决方法二:同名覆盖,再造接口在C 中再声明一个同名成员函数f(),该函数根据需要调用A::f() 或B::f()

 

路径二义性

 

 

为了解决路径二义性问题,引入虚基类

–用于有共同基类的多继承场合(多层共祖)

声明
–以virtual修饰说明共同的直接基类
例:class B1: virtual public B
作用
–用来解决多继承时可能发生的对同一基类继承多次和多层而产生的二义性问题.
–为最远的派生类提供唯一的基类成员,而不重复产生个副本。
注意:
–在第一级继承时就要将共同基类设计为虚基类。

虚基类举例
class B { public: int b;};
class B1 : virtual public B { private: int b1;};
class B2 : virtualpublic B { private: int b2;};
class C: public B1, public B2{ private: float d;};
在子类对象中,最远基类成分是唯一的。于是下面的访问是正确的:
C cobj;
cobj.b;

使用最远基类成员原则

 

[cpp] view plain copy
 
  1. //使用最远基类成员原则  
  2. #include <iostream>  
  3. using namespace std;  
  4. class B0//声明基类B0  
  5. {   
  6. public://外部接口  
  7.     int nV;  
  8.     void fun(){cout<<"Member of B0"<<endl;}  
  9. };  
  10. class B1: virtual public B0 //B0为虚基类,派生B1类  
  11. {   
  12.     public://新增外部接口  
  13.     int nV1;  
  14. };  
  15. class B2: virtual public B0 //B0为虚基类,派生B2类  
  16. {   
  17.     public://新增外部接口  
  18.     int nV2;  
  19. };  
  20.   
  21. class D1: public B1, public B2//派生类D1声明  
  22. {  
  23.     public://新增外部接口  
  24.     int nVd;  
  25.     void fund(){cout<<"Member of D1"<<endl;}  
  26. };  
  27. void main()//程序主函数  
  28. {  
  29.     D1 d1;//声明D1类对象d1  
  30.     d1.nV=2;//使用最远基类成员  
  31.     d1.fun();  
  32. }  

运行结果:

 

Member of B0
Press any key to continue

有虚基类时的构造函数的调用次序:

无论虚基类与产生对象的派生类相隔多远,首先调用虚基类的构造函数;

然后按继承次序调用直接基类的构造函数;

如果有包含的对象,再按声明次序调用所包含对象类的构造函数;

最后才是普通类型数据成员的初始化。

 

有虚基类时的构造函数举例

 

[cpp] view plain copy
 
  1. //有虚基类时的构造函数举例  
  2. #include <iostream>  
  3. using namespace std;  
  4. class B0//声明基类B0  
  5. {   
  6.     public://外部接口  
  7.     B0(int n){ nV=n;cout<<"B0's constructor called \n";}  
  8.     int nV;  
  9.     void fun(){cout<<"Member of B0"<<endl;}  
  10. };  
  11. class B1: virtual public B0  
  12. {   
  13. public:  
  14.     B1(int a) : B0(a) {cout<<"B1's constructor called \n";}  
  15.     int nV1;  
  16. };  
  17. class B2: virtual public B0  
  18. {   
  19. public:  
  20.     B2(int b) : B0(b) {cout<<"B2's constructor called \n";}  
  21.     int nV2;  
  22. };  
  23. class D1: public B1, public B2  
  24. {  
  25. public:  
  26.     D1(int c) : B0(c), B1(c), B2(c),b1(c),b2(c) {cout<<"D1's constructor called \n"; }  
  27.     int nVd;  
  28.     void fund(){cout<<"Member of D1"<<endl;}  
  29. private:  
  30.     B1 b1;  
  31.     B2 b2;  
  32. };  
  33. void main()  
  34. {  
  35.     D1 d1(1);  
  36.     d1.nV=2;  
  37.     d1.fun();  
  38. }  

运行结果:

 

B0's constructor called
B1's constructor called
B2's constructor called
B0's constructor called
B1's constructor called
B0's constructor called
B2's constructor called
D1's constructor called
Member of B0
Press any key to continue

 

“二义性”的回顾

凡是编译器访问或调用时有多于一项的合法选择,这种会让编译器举棋不定的代码叫具二义性的代码。
二义性的发生场合:
1.带默认形参值的函数与同名的重载函数相遇时;

 

[cpp] view plain copy
 
  1. //带默认形参值的函数与同名的重载函数相遇时  
  2. #include <iostream>  
  3. using namespace std;  
  4. void funct (int i, long =2,float f=3);     //函数原型中默认值可省去形参名  
  5. void funct (int i, long l)    {     l=i+l;    }  
  6.   
  7. void main()  
  8. {  
  9.     funct(2);           //调用void funct(int i,long =2,float f=3);  
  10.     /* 
  11.     funct (2,3)可以匹配3个形参的funct (int i, long=2,float f=3) 
  12.     也可以匹配2个形参的funct(int i,long l)。因此程序在编译时出现提示 
  13.     */  
  14.     //funct(2,3);     // error take place  here,  
  15.     funct(2,3,4);    //调用void funct (int, long, float);       
  16. }  
  17.   
  18. void funct (int i, long l,float f)  
  19. {  
  20.     cout << "funct(int i, long =2,float f=3)"<<endl;  
  21. }  


2.继承时的同名二义;
3.多层共祖的路径二义;
4.形实结合时的类型兼容;

[cpp] view plain copy
 
  1. //形实结合时的类型兼容;  
  2. #include <iostream>  
  3. using namespace std;  
  4. void fun ( int a){cout << "fun(int)" <<endl;}  
  5. void fun ( char c){cout << "fun(char)" <<endl;}  
  6.   
  7. void main()  
  8. {  
  9.     double d = 100.618;  
  10.     //fun ( d ); // 到底调用哪个函数?  
  11.     //解决:  
  12.     fun( int(d) );   
  13.     fun( char(d) );  
  14.     //或  
  15.     fun( static_cast<int>(d) );  
  16.     fun( static_cast<char>(d) );    
  17. }  

运行结果:

 

fun(int)
fun(char)
fun(int)
fun(char)
Press any key to continue

5.用户自定义类型的转换时。

class B;

class A 

public: 

A (const B &) {} //构造函数

};

class B 

public: 

operator A ( ) const {} //类型转换函数

};

void fun ( const A &);

B b;fun ( b ); // 到底调用哪个函数?  类型转换函数还是构造函数

解决:1。给A的构造函数前加explicit ,关闭自动转换功能;2。不提供或不直接提供B类的转换函数。