原创 C++多继承的二义性问题

2018-2-10 21:37 2253 21 2 分类: 软件与OS 文集: C++学习记录(1)

     在多重继承中,需要解决的主要问题是标识符不唯一,即二义性问题。比如,当在派生类继承的多个基类中有同名成员时,派生类中就会出现标识符不唯一的情况。

     在多重继承中,派生类由多个基类派生时,基类之间用逗号隔开,且每个基类前都必须指明继承方式,否则默认是私有继承。可以通过以下3种方法解决二义性问题。

  •  使用运算符"::";
  •  使用同名覆盖的原则;
  •  使用虚基类;

1.     使用域运算符

     如果派生类的基类之间没有继承关系,同时又没有共同的基类,则在引用同名成员时,可以在成员名前面加上类名和域运算符来区别来自不同基类的成员。

2.使用同名覆盖的原则

    在派生类中重新定义与基类中同名的成员(如果是成员函数,则参数表也要相同,否则是重载)以屏蔽掉基类中的同名成员,在引用这些同名成员时,引用的就是派生类中的成员。

#include<iostream>

using namespace std;

class Base

{

public:

int x;

void show()

{

cout << "Base,x= " << x << endl;

}

};

class Derived :public Base

{

public:

int x;

void show()

{

cout << "Derived , x= " << x << endl;

}

};

int main()

{

Derived ob;

ob.x = 5;

ob.show();

ob.Base::show();

ob.Base::x = 12;

ob.Base::show();

system("pause");

return 0;

}

3.虚基类

     在多重继承中,要引用派生类的成员时,先是在派生类自身的作用域内寻找,如果找不到再到基类中寻找。这时,如果这些基类有一个共同的基类,派生类访问这个共同基类的成员时,就有可能由于同名成员的问题而发生二义性,此时就需要虚基类来解决。

#include<iostream>

using namespace std;

class Base

{

public:

Base()

{

x = 1;

}

protected:

int x;

};

class Base1 :public Base

{

public:

Base1()

{

cout << "Base1,x= " << x << endl;

}

};

class Base2 :public Base

{

public:

Base2()

{

cout << "Base2,x= " << x << endl;

}

};

class Derived :public Base1, public Base2

{

public:

Derived()

{

cout << "Derived,x= " << x << endl;

}

};

int main()

{

Derived obj;

system("pause");

return 0;

}

     上边的代码表面上看类Base1和类Base2是从同一个基类Base派生出来的,但是其对应的却是基类Base的两个不同的复制。因此,当派生类Derived要访问变量x时不知从哪条路径去寻找,从而引发二义性问题。


     上述代码对应的类层次结构如图1所示,属于非虚基类的类层次结构。要解决该问题,需要引入虚基类,其具体的做法是将公共基类声明为虚基类,这样这个公共基类就只有一个拷贝,从而不会出现二义性问题。虚基类的类层次结构如图2所示。

         

                                                     图 1

          

                                                          图 2                                        

3.1 虚基类

 虚基类的声明是在派生类的声明过程中进行的,其声明的一般形式为:

      class<派生类名>virtual <派生方式><基类名>

这种派生方式叫做虚拟继承,虚基类关键字的作用范围和派生方式与一般派生类的声明一样,只对紧跟其后的基类起作用。声明了虚基类以后,虚基类的成员在进一步的派生过程中和派生类一起维护同一个内存拷贝。

#include<iostream>

using namespace std;

class Base

{

public:

Base()

{

x = 1;

}

protected:

int x;

};

class Base1 :virtual public Base

{

public:

Base1()

{

cout << "Base1,x= " << x << endl;

}

};

class Base2 :virtual public Base

{

public:

Base2()

{

cout << "Base2,x= " << x << endl;

}

};


class Derived :public Base1, public Base2

{

public:

Derived()

{

cout << "Derived,x= " << x << endl;

}

};

int main()

{

Derived obj;

system("pause");

return 0;

}

    在上述代码中,由于把公共基类Base声明为类Base1和Base2的虚基类,所以由类Base1和类Base2派生的类Derived只有一个基类Base,消除了二义性。

3.2 虚基类构造函数和初始化

虚基类的初始化与一般的多继承的初始化在语法上是一样的,但是构造函数的执行顺序不同。主要在以下方面:

  • 虚基类的构造函数的执行在非虚基类的构造函数之前;
  • 若同一层次中包含多个虚基类,这些虚基类的构造函数按照他们被声明的先后顺序执行;
  • 若虚基类由非虚基类派生而来,则仍然先执行基类的构造函数,再执行派生类的构造函数。

#include<iostream>

using namespace std;

class Base

{

public:

Base(int x1)

{

x = x1;

cout << "Base,x= " << x << endl;

}

protected:

int x;

};

class Base1 :virtual public Base

{

int y;

public:

Base1(int x1, int y1) :Base(x1)

{

y = y1;

cout << "Base1 ,y=" << y << endl;

}

};

class Base2 :virtual public Base

{

int z;

public:

Base2(int x1, int z1) :Base(x1)

{

z = z1;

cout << "Base2,z= " << z << endl;

}

};

class Derived :public Base1, public Base2

{

int xyz;

public:

Derived(int x1, int y1, int z1, int xyz1) :Base(x1), Base1(x1,y1), Base2(x1,z1)

{

xyz = xyz1;

cout << "Derived,xyz = " << xyz << endl;

}

};

int main()

{

Derived obj(1, 2, 3, 4);

system("pause");

return 0;

}

       上边的代码中,虚基类Base的构造函数只执行了一次,这是因为当派生类Derived调用了虚基类Base的构造函数之后,类Base1Base2对虚基类Base构造函数的调用就被忽略,这是初始化虚基类和初始化非虚基类的不同。

在使用虚基类时要注意:

  •    虚基类的关键字virtual与派生方式的关键字public,private,protected 的书写位置无关紧要,可以先写虚基类的关键字,也可以先写派生    方式的关键字;
  •    一个基类在作为某些类的虚基类的同时可以作为另一些类的非虚基类;
  •    虚基类构造函数的参数必须由最新派生出来的类负责初始化,即使不是直接继承也应如此。



文章评论0条评论)

登录后参与讨论
我要评论
0
21
关闭 站长推荐上一条 /2 下一条