1. 程式人生 > >C++多繼承中二義性的解決方案

C++多繼承中二義性的解決方案

        出現二義性的原因: 派生類在訪問基類成員函式時,由於基類存在同名的成員函式,導致無法確定訪問的是哪個基類的成員函式,因此出現了二義性錯誤

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 B1{
        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並沒有初始化,依舊存放的垃圾資料。