C++ day26 代码重用(二) 多重继承(MI, multiple inheritance,主要研究多重公有继承)

多重继承

前面说的都是单继承,即只有一个基类。多重继承是有两个甚至更多的基类。

公有单继承和公有多重继承(基类都用public修饰)都表示is-a关系。

私有继承和保护继承描述的都是has-a关系。

多重继承带来的两个主要问题

反正和单继承相比,多重继承肯定是更难的,很容易出现问题。因此有很多C++程序员希望删除多重继承特性,但也有一部分人觉得MI对于一些比较特殊的工程来说是特别有用的,必不可少的,所以MI被保留了,但是一定要谨慎地使用。。。

从两个不同基类继承同名方法

从两个或者更多相关基类继承同一个类的多个实例

示例 多重公有继承

worker是一个抽象基类,即有纯虚函数的基类。用它派生出singer类和waiter类,再从singer和waiter类中派生出SingingWaiter类
在这里插入图片描述

代码

先不写SingingWaiter类

//Worker.h
#ifndef WORKER_H_
#define WORKER_H_
#include <string>
class Worker// an abstract base class,不可以创建抽象基类的对象
{
private:
	std::string fullname;//包含
	long id;
public:
	Worker():fullname("no one"), id(0L){}
	explicit Worker(const std::string & st):fullname(st), id(0L){}
	explicit Worker(long ID):fullname("no one"), id(ID){}
	Worker(const std::string & st, long ID):fullname(st), id(ID){}
	//纯虚析构函数,基类的析构函数必须是虚的,抽象基类至少有一个纯虚函数,在派生类中抽象基类的纯虚函数自动成为虚函数
	virtual ~Worker() = 0;
	virtual void Set();
	virtual void Show() const;
};

class Waiter:public Worker // 公有继承,继承了一个Worker组件
{
private:
	int panache;
public:
	//默认参数在构造函数中的好处:一个构造函数相当于两个
	explicit Waiter(int pa = 0):Worker(), panache(pa){}//Worker()是调用基类Worker的默认构造函数,不给参数
	explicit Waiter(const std::string & st, int pa = 0):Worker(st), panache(pa){}
	explicit Waiter(long ID, int pa = 0):Worker(ID), panache(pa){}
	//有默认参数就不需要Waiter(const std::string & st, long ID):Worker(st, ID), panache(0){}了
	Waiter(const std::string & st, long ID, int pa = 0):Worker(st, ID), panache(pa){}
	Waiter(const Worker & wk, int pa = 0):Worker(wk), panache(pa){}//虽然抽象基类不可有对象,但是可以在形参这么写,会进行类型转换
	~Waiter(){}//和不写一样,会自动调用Worker的析构函数
	void Set();
	void Show() const;
};

class Singer:public Worker//继承了一个Worker组件
{
protected://把一些整型常量定义为保护数据成员,即对于派生链条上的类而言相当于公有数据成员,对于外界相当于私有成员
	enum {other, alto, contralto, soprano, bass, baritone, tenor};
	enum {Vtypes = 7};
private:
	//静态私有成员是类的所有对象共享的,不属于任何一个对象
	static const char *pv[Vtypes];//char指针数组,每个char指针指向一个字符串,表示一个声音类型
	int voice;
public:
	Singer(int vo = 0):Worker(), voice(vo){}
	Singer(const std::string & st, long ID, int vo = other):Worker(st, ID), voice(vo){}//vo的默认值写0或other都可以,最好写other
	Singer(const Worker & wk, int vo = 0):Worker(wk), voice(vo){}
	void Set();
	void Show() const;
};
#endif
//Worker.cpp
#include <string>
#include <iostream>
#include "Worker.h"
using std::cin;
using std::cout;
using std::string;
using std::endl;

//Worker methods
//虚析构函数必须实现,即使是纯虚析构
Worker::~Worker(){}

void Worker::Set()//定义不写virtual关键字
{
	cout << "Enter worker's full name:\n";
	getline(cin, fullname);//iostream类的函数
	cout << "Enter worker's ID:\n";
	cin >> id;
	//清空输入缓冲
	while (cin.get() != '\n')
		;
}

void Worker::Show() const
{
	cout << "Worker Name: " << fullname << endl;
	cout << "Worker ID: " << id << endl;
}

//Waiter methods
void Waiter::Set()
{
    cout << "Worker Category: Waiter\n";
	Worker::Set();
	cout << "Enter waiter's panache rating:\n";
	cin >> panache;
	while (cin.get() != '\n')
		;
    cout << endl;
}

void Waiter::Show() const
{
	/*
	*错误代码:派生类不可以直接访问基类的私有成员,只可以用基类的公有方法去访问
	*cout << "Waiter Name: " << fullname << endl;
	*cout << "Waiter ID: " << id << endl;
	*/
	cout << "Worker Category: Waiter\n";
	Worker::Show();
	cout << "Panache rating: " << panache << endl;
	cout << endl;
}

//Singer methods
//先给静态char指针数组赋值,虽然不是函数,但也可以写在实现文件中
 const char * Singer::pv[] = {"other", "alto", "contralto", "soprano", "bass", "baritone", "tenor"};//一定要用类名限定!!!像方法一样

void Singer::Set()
{
    cout << "Worker Category: Singer\n";
	Worker::Set();
	cout << "Enter singer's voice type (integer 0 - 6):\n";
	//展示给用户看所有可供选择的类型
	int i;
	for (i = 0; i < Vtypes; ++i)
	{
		cout << i << ": " << pv[i] << "  ";
	}
	cout << endl;
	//保证输入正确
	while (cin >> voice && (voice >= Vtypes || voice < 0))
		cout << "Please enter a value >= 0 and < " << Vtypes << endl;
	//清空输入队列
	while (cin.get() != '\n')
		;
    cout << endl;
}

void Singer::Show() const
{
    cout << "Worker Category: Singer\n";
	Worker::Show();
	cout << "Singer's voice type: " << pv[voice] << endl;
	cout << endl;
}
#include <iostream>
#include "Worker.h"
const int LIM = 4;
using std::cout;

//使用一个多态指针数组调用同名方法
int main()
{
    Waiter bob("Bob Apple", 123L, 4);
	Singer mary("Mary Smith", 156L, 2);
	Waiter anna;
	Singer amy;

	Worker *pw[LIM] = {&bob, &mary, &anna, &amy};//抽象基类的指针数组,bob和mary只是把他们的Worker组件(基类对象)的地址赋给了指针
	int i;
	for (i = 2; i < LIM; ++i)
		pw[i]->Set();
	for (i = 0; i < LIM; ++i)
		pw[i]->Show();
		
    return 0;
}

输出

Worker Category: Waiter
Enter worker's full name:
Anna Drop
Enter worker's ID:
456
Enter waiter's panache rating:
4

Worker Category: Singer
Enter worker's full name:
Sia Taylor
Enter worker's ID:
1268
Enter singer's voice type (integer 0 - 6):
0: other  1: alto  2: contralto  3: soprano  4: bass  5: baritone  6: tenor
3

Worker Category: Waiter
Worker Name: Bob Apple
Worker ID: 123
Panache rating: 4

Worker Category: Singer
Worker Name: Mary Smith
Worker ID: 156
Singer's voice type: contralto

Worker Category: Waiter
Worker Name: Anna Drop
Worker ID: 456
Panache rating: 4

Worker Category: Singer
Worker Name: Sia Taylor
Worker ID: 1268
Singer's voice type: soprano
出现的问题
  • 在派生类中企图使用基类的私有变量。。。必须要用基类公有方法才可以访问到,除非是保护成员变量
  • Singer(const std::string & st, long ID, int vo = other):Worker(st, ID), voice(vo){}中的, voice(vo)忘记写,导致了Singer mary(“Mary Smith”, 156L, 2);没有成功构造出想要的对象,但是这种情况不会在编译时发现,只会在运行时出错,刚开始还不知道为啥,调试发现voice的值打印不出来才想到这边出问题了。
  • 忘记写const char * Singer::pv[] = {“other”, “alto”, “contralto”, “soprano”, “bass”, “baritone”, “tenor”};中的类名限定符,在方法定义文件中定义静态变量,必须和方法一样,用类名限定起来,不然编译器不知道pv到底是哪个类的,尽管头文件包含此信息,但是编译器就是要你写清楚。
做的好的地方
  • const char * Singer::pv[] = {“other”, “alto”, “contralto”, “soprano”, “bass”, “baritone”, “tenor”};中书上代码没有const,于是报7个警告(一个词语一个),说C++禁止把string constant转换为char *类型,我就明白了,这属于const赋给非const的情况,当然会报错,于是这么改了就好了(头文件中也要加const:static const char *pv[Vtypes];)。
    在这里插入图片描述
学到或加深印象的地方
  • 基类指针数组实现多态,其中基类甚至可以是抽象基类。
  • 用enum枚举定义整型常量
  • 指针数组
  • 静态数据成员可以在方法文件中赋值
  • 基类的析构函数必须是虚函数,且虚析构函数必须要实现,即使是纯虚析构函数!
  • 检查输入是否正确(voice的输入必须是0-6)

由SingingWaiter类的问题引出虚基类

SingingWaiter类的问题:将继承两个Worker对象,使多态调用方法时因二义性而不可行

这里并不是真的说有Worker对象,因为毕竟抽象基类是不可以创建对象的,这么说仅仅是为了便于表达,不过还是说组件吧更严谨。

class SingingWorker:public Singer, public Waiter//必须每个基类都用public修饰,因为默认的是private
{...};

由于 Waiter类和Singer类都继承了一个Worker组件,所以SingingWaiter有两个Worker组件,但是其实只需要一个Worker组件就好了,因为Singer类和Waiter类的Worker组件都一样又没区别。另外,这会在多态调用时造成二义性,使得程序出错:

SingingWaiter ed;
Worker *pw = &ed;//ed包含两个Worker组件,即有两个地址,于是编译器不知道要让pw指向谁

在这里插入图片描述

解决这种二义性要依靠类型转换:

Worker * pw1 = (Waiter *) &ed;//指向Waiter中的Worker组件
Worker * pw2 = (Singer *) &ed;

所以这种多重继承使得利用基类指针引用不同类的对象的多态性变复杂了。要多加一个类型转换才行。

但是光做个类型转换确实解决了这里的二义性问题,但只是解决了皮毛,并不釜底抽薪之举,因为如前所述,根本不需要两个Worker组件啊,再多的Worker组件都是一毛一样的,我们只需要一个!SingingWaiter类的对象应该只拥有一个Worker组件,即只有一个名字和一个ID。

这该怎么办呢?

于是C++迫不得已,专门为多重继承引入一个新技术——虚基类(virtual base class)。

虚基类

虚基类专门针对上面说的这种情况:让两个继承同一个基类(第一代类)的类(第二代类)共同派生的类(第三代类)的对象只有一个基类(第一代类)对象。

class Singer:virtual public Worker{...}; 
class Waiter:public virtual Worker{...};
class SingingWaiter: public Singer, public Waiter{...};//则SingingWaiter对象只包含一个Worker组件的一个副本,即Singer类和Waiter类实际上共享了同一个Worker组件,而不是各自都引入一个Worker对象副本

public 和 virtual的顺序无所谓

在这里插入图片描述

需要注意的几点:

  • 虚基类中的"虚"和虚函数的“虚”不是一回事,没有关系。由于C++用户墙裂反对再引入新的关键字(已经够多了),所以就用了virtual。这实际上是一种关键字重载。
  • 有时候可能真的需要基类对象的多个副本,于是就不需要使用虚基类。这种情况虽然少,也是有的。

虚基类的构造函数在派生链条中不可以自动传递信息

以前的非虚基类:

class A
{
private:
	int a;
public:
	A(int m = 0):a(m){}
};

class B: public A
{
private:
	int b;
public:
	B(int m = 0, int n = 0):A(m), b(n){}
};

class C:public B
{
private:
	int c;
public:
	C(int m = 0, n = 0, q = 0):B(m, n), c(q){}
};

可以看到,非虚基类中, B继承了A, C继承了B,在这个继承链条中,C只可以调用B的构造函数,不可以调用A的构造函数,因为B会自动调用A的构造函数。其中C把信息传给了B,B又把信息传给了A,相当于B是中间类,C通过中间了自动地把信息传给了基类A。

但是在虚基类中,这种在派生链条中通过中间类自动传递信息将不再行得通:

SingingWaiter(const Worker & wk, int pa = 0, int vo = Singer::other):Waiter(wk, pa), Singer(wk, vo){};

这句代码中,如果可以通过中间类自动传递信息,则Waiter和Singer的构造函数都会自动调用Worker类的构造函数,所以有2条不同路径都会把wk传给Worker基类,会造成冲突。

所以C++就规定:当基类是虚的时,禁止信息通过中间类自动传递给基类。所以上面的代码只会初始化panache和voice两个参数,那基类对象的那个组件怎么办呢?答案是:编译器调用Worker的默认构造函数去创建。
那如果不想用默认构造函数呢?则:

SingingWaiter(const Worker & wk, int pa = 0, int vo = Singer::other):Worker(wk), Waiter(wk, pa), Singer(wk, vo){};

注意这里相当于在第三代类的构造函数中使用了第一代类的构造函数,这在非虚基类是绝对不可以的!(非虚基类只允许在构造函数中调用派生链条中相邻的基类的构造函数)。

用虚基类写SingingWaiter类

前面三个类的代码也做了一些改动

//Worker.h
#ifndef WORKER_H_
#define WORKER_H_
#include <string>
class Worker// an abstract base class,不可以创建抽象基类的对象
{
private:
	std::string fullname;//包含
	long id;
protected:
	virtual void Data() const;
	virtual void Get();
public:
	Worker():fullname("no one"), id(0L){}
	explicit Worker(const std::string & st):fullname(st), id(0L){}
	explicit Worker(long ID):fullname("no one"), id(ID){}
	Worker(const std::string & st, long ID):fullname(st), id(ID){}
	//纯虚析构函数,基类的析构函数必须是虚的,抽象基类至少有一个纯虚函数,在派生类中抽象基类的纯虚函数自动成为虚函数
	virtual ~Worker() = 0;
	virtual void Set() = 0;
	virtual void Show() const = 0;
};

class Waiter:virtual public Worker // 公有继承
{
private:
	int panache;
protected:
	void Data() const;
	void Get();
public:
	//默认参数在构造函数中的好处:一个构造函数相当于两个
	explicit Waiter(int pa = 0):Worker(), panache(pa){}//Worker()是调用基类Worker的默认构造函数,不给参数
	explicit Waiter(const std::string & st, int pa = 0):Worker(st), panache(pa){}
	explicit Waiter(long ID, int pa = 0):Worker(ID), panache(pa){}
	//有默认参数就不需要Waiter(const std::string & st, long ID):Worker(st, ID), panache(0){}了
	Waiter(const std::string & st, long ID, int pa = 0):Worker(st, ID), panache(pa){}
	Waiter(const Worker & wk, int pa = 0):Worker(wk), panache(pa){}//虽然抽象基类不可有对象,但是可以在形参这么写,会进行类型转换
	~Waiter(){}//和不写一样,会自动调用Worker的析构函数
	void Set();
	void Show() const;
};

class Singer: virtual public Worker
{
protected://把一些整型常量定义为保护数据成员,即对于派生链条上的类而言相当于公有数据成员,对于外界相当于私有成员
	enum {other, alto, contralto, soprano, bass, baritone, tenor};
	enum {Vtypes = 7};
	void Data() const;
	void Get();
private:
	//静态私有成员是类的所有对象共享的,不属于任何一个对象
	static const char *pv[Vtypes];//char指针数组,每个char指针指向一个字符串,表示一个声音类型
	int voice;
public:
	Singer(int vo = 0):Worker(), voice(vo){}
	Singer(const std::string & st, long ID, int vo = other):Worker(st, ID), voice(vo){}//vo的默认值写0或other都可以,最好写other
	Singer(const Worker & wk, int vo = 0):Worker(wk), voice(vo){}
	void Set();
	void Show() const;
};

class SingingWaiter: public Singer, public Waiter
{
protected:
	void Data() const;
	void Get();
public:
    SingingWaiter(){}
	SingingWaiter(const std::string & st, long ID, int vo = Singer::other, int pa = 0)
        : Worker(st, ID), Singer(st, ID, vo), Waiter(st, ID, pa){}
	SingingWaiter(const Worker & wk, int vo = Singer::other, int pa = 0)
        : Worker(wk), Singer(wk, vo), Waiter(wk, pa){}
	SingingWaiter(const Waiter & wa, int vo = other):Singer(vo), Waiter(wa){}//必须先写Singer,否则警告(这个顺序必须按照声明类时的public Waiter, public Singer来)
	SingingWaiter(const Singer & si, int pa = 0):Singer(si), Waiter(pa){}
	void Show() const;
	void Set();
};
#endif
//Worker.cpp
#include <string>
#include <iostream>
#include "Worker.h"
using std::cin;
using std::cout;
using std::string;
using std::endl;

//Worker methods
//虚析构函数必须实现,即使是纯虚析构
Worker::~Worker(){}
void Worker::Data() const
{
	cout << "Worker Name: " << fullname << endl;
	cout << "Worker ID: " << id << endl;
}

void Worker::Get()
{
	cout << "Enter worker's full name:\n";
	getline(cin, fullname);//iostream类的函数
	cout << "Enter worker's ID:\n";
	cin >> id;
	//清空输入缓冲
	while (cin.get() != '\n')
		;
}

//Waiter methods
void Waiter::Data() const
{
	cout << "panache rating: " << panache << endl;
}

void Waiter::Get()
{
    cout << "Enter waiter's panache rating:\n";
	cin >> panache;
	while (cin.get() != '\n')
		;
}

void Waiter::Set()
{
    cout << "Worker Category: Waiter\n";
	Worker::Get();
	Get();
    cout << endl;
}

void Waiter::Show() const
{
	cout << "Worker Category: Waiter\n";
	Worker::Data();
	Data();
	cout << endl;
}

//Singer methods
//先给静态char指针数组赋值,虽然不是函数,但也可以写在实现文件中
const char * Singer::pv[] = {"other", "alto", "contralto", "soprano", "bass", "baritone", "tenor"};//一定要用类名限定!!!像方法一样

void Singer::Data() const
{
	cout << "Singer's voice type: " << pv[voice] << endl;
}

void Singer::Get()
{
	cout << "Enter singer's voice type (integer 0 - 6):\n";
	//展示给用户看所有可供选择的类型
	int i;
	for (i = 0; i < Vtypes; ++i)
	{
		cout << i << ": " << pv[i] << "  ";
	}
	cout << endl;
	//保证输入正确
	while (cin >> voice && (voice >= Vtypes || voice < 0))
		cout << "Please enter a value >= 0 and < " << Vtypes << endl;
	//清空输入队列
	while (cin.get() != '\n')
		;
}

void Singer::Set()
{
    cout << "Worker Category: Singer\n";
	Worker::Get();
	Get();
    cout << endl;
}

void Singer::Show() const
{
    cout << "Worker Category: Singer\n";
	Worker::Data();
	Data();
	cout << endl;
}

//SingingWaiter methods
void SingingWaiter::Data() const
{
	Singer::Data();
	Waiter::Data();
}

void SingingWaiter::Get()
{
	Singer::Get();
	Waiter::Get();
}

void SingingWaiter::Set()
{
	cout << "Worker Category: Singing Waiter\n";
	Worker::Get();
	Get();
}

void SingingWaiter::Show() const
{
	cout << "Worker Category: Singing Waiter\n";
	Worker::Data();
	Data();
}
#include <iostream>
#include <cstring>
#include "Worker.h"
const int SIZE = 5;
using std::cout;
using std::cin;

//使用一个多态指针数组调用同名方法
int main()
{
    SingingWaiter amy("Amy Smith", 152L, 2, 3);
	amy.Waiter::Show();//不用类名限定会导致二义性,因为SingingWaiter的两个基类Singer类和Waiter类都有Show()方法,自己又没定义Show()方法
	amy.Singer::Show();

	Worker *pw[SIZE];
	int ct;
	for (ct = 0;ct < SIZE; ++ct)
	{
		char choice;
		cout << "Enter the employee category:\n";
		cout << "w: waiter  s: singer\n"
			 << "t: singing waiter q: quit\n";

		while (cin >> choice && (std::strchr("wstq", choice) == NULL))
		{
			cout << "Please enter w, s, t, or q: ";
		}
		if (choice == 'q')
			break;
		switch(choice)
		{
			case 'w':
				pw[ct] = new Waiter; break;
			case 's':
				pw[ct] = new Singer; break;
			case 't':
				pw[ct] = new SingingWaiter; break;
		}
		cin.get();//这句必不可少!!!cin >> choice把输入队列的字母给了choice,但是仍然还在输入队列中,要用cin.get()才可以读掉,否则后面没法输入名字
		pw[ct]->Set();

	}

	cout << "Here is your staff:\n";
	int i;
	for (i = 0; i < ct; ++i)
		{
			pw[i]->Show();
			cout << '\n';
		}
	for (i = 0; i < ct; ++i)
		delete pw[i];

    return 0;
}

输出

Worker Category: Waiter
Worker Name: Amy Smith
Worker ID: 152
Singer's voice type: contralto
panache rating: 3

Worker Category: Singer
Worker Name: Amy Smith
Worker ID: 152
Singer's voice type: contralto
panache rating: 3

Enter the employee category:
w: waiter  s: singer
t: singing waiter q: quit
w
Worker Category: Waiter
Enter worker's full name:
Anna Smith
Enter worker's ID:
343
Enter waiter's panache rating:
2

Enter the employee category:
w: waiter  s: singer
t: singing waiter q: quit
s
Worker Category: Singer
Enter worker's full name:
Joy Sue
Enter worker's ID:
4245
Enter singer's voice type (integer 0 - 6):
0: other  1: alto  2: contralto  3: soprano  4: bass  5: baritone  6: tenor
5

Enter the employee category:
w: waiter  s: singer
t: singing waiter q: quit
t
Worker Category: Singing Waiter
Enter worker's full name:
Mary Hellon
Enter worker's ID:
125
Enter singer's voice type (integer 0 - 6):
0: other  1: alto  2: contralto  3: soprano  4: bass  5: baritone  6: tenor
4
Enter waiter's panache rating:
6
Enter the employee category:
w: waiter  s: singer
t: singing waiter q: quit
q
Here is your staff:
Worker Category: Waiter
Worker Name: Anna Smith
Worker ID: 343
panache rating: 2


Worker Category: Singer
Worker Name: Joy Sue
Worker ID: 4245
Singer's voice type: baritone


Worker Category: Singing Waiter
Worker Name: Mary Hellon
Worker ID: 125
Singer's voice type: bass
panache rating: 6

MI的其他复杂问题

混合使用虚基类和非虚基类会导致派生类对象中有几个基类组件

比如类A被作为虚基类派生出类B和C,又被作为非虚基类派生出类D和E,然后类B,C,D,E共同派生了类F。那么F类的对象中有几个A类的组件(对象)呢?

答案是3个。因为B和C由于把A当做虚基类继承,所以F中从BC这里只会得到一个A的对象组件,BC共享这一个组件;而DE是非虚基类继承,则每人都有一个A类对象组件,于是在F类中一共就有三个A类的对象组件。

反正看有几条路径。每个非虚基类都提供一条路径,所有虚基类一起提供一条路径。

一般情况下,这种基类的多个对象组件副本都是棘手的问题,而不是所希望的;只有极少数的特殊编程问题中才需要多个基类对象副本,以后遇到再说。

虚基类略改变了C++解析二义性的方式

怎么改变的呢?之前是只有类名限定,现在是先用优先顺序,优先顺序无法消除二义性才上类名限定

如果是只用非虚基类,那么如果类从不同的类中继承了两个或更多的同名方法或同名数据,那么只需要使用类名限定就可以消除调用这个方法的二义性了。

如果在非虚基类中,其实问题可以更简单一丁点:即有时候不需要类名限定也没有二义性的问题,这是因为虚基类会使得同名方法之间有个优先顺序!只要优先顺序可以消除二义性就不需要类名限定了;有时候(优先顺序一样,消除不了二义性)则还是和非虚基类一样,需要类名限定以消除二义性。

注意只有虚基类才有同名方法之间的优先顺序哦,非虚基类没有这个说法。并且虽然用优先顺序可以消除二义性的时候不需要类名限定,但是这需要自己很清楚优先顺序的判断,别搞错了。

什么优先顺序?一个成员名如何优先于另一个成员名?
答案:派生类的名称优先于直接或间接祖先类(隔代亲,隔一代或多代)中的相同名称

举个栗子:

class B
{
public:
	short q();
};

class C: virtual public B//把B当做虚基类继承,只要这里使用virtual,则基类B成为虚基类
{
public:
	long q();
	int omg();
};

class D: public C
{};

class E: virtual public B
{
private:
	int omg();
};

class F: public D, public E
{};

草画了一个继承图
在这里插入图片描述
F类的q()方法: F类从D,E两条路径都集继承到了名为q的方法,我们要分析优先性。先看D路径,D类没有新方法,其q()方法来自于公有继承C类的公有q()方法;再看E路径,E的q()来自于虚公有继承B类的公有q()方法。看继承图发现,C类的q()优先于B类的q(),所以F类中调用q()方法没有二义性,相当于C::q()。但是如果你真的想用B类的,则用B::q()即可。

F类的omg()方法:F类从D,E两条路径都集继承到了名为omg的方法,我们要分析优先性。先看D路径,D类没有新方法,其omg()方法来自于公有继承C类的公有omg()方法;再看E路径,E的omg()是自己的私有方法。看继承图发现,C类和E类不是派生和被派生的关系,所以他们的同名方法没有优先顺序一说,所以F类中调用omg()方法有二义性,虽然不可以在F类中使用E的私有方法,但使用omg()还是要必须明确指出C::omg()。

总之,使用虚基类会增加一点复杂性,多判断一个优先顺序,主要是多重继承中的最终派生类会通过多条途径继承同一个基类引起的。总之,使用MI要慎之又慎,一定要注意类名限定和虚基类的优先规则。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
多重继承中的义性问题指的是当一个派生类同时继承自两个或多个具有相同成员函数或成员变量的基类时,可能会导致成员函数或成员变量的义性。 考虑以下示例代码: ```cpp class Base1 { public: void display() { std::cout << "Base1 display()" << std::endl; } }; class Base2 { public: void display() { std::cout << "Base2 display()" << std::endl; } }; class Derived : public Base1, public Base2 { public: void show() { // display(); // 义性错误,无法确定调用哪个基类的 display() Base1::display(); // 显式指定调用 Base1 的 display() Base2::display(); // 显式指定调用 Base2 的 display() } }; ``` 在上述代码中,Derived 类同时继承自 Base1 和 Base2 两个基类,并且这两个基类都有一个名为 `display()` 的成员函数。当在 Derived 类中调用 `display()` 函数时,由于存在两个同名函数,编译器无法确定调用哪个基类的 `display()` 函数,从而导致义性错误。 为了解决这个问题,我们可以使用作用域解析运算符 `::` 来显式地指定调用哪个基类的成员函数,如在上述代码中的 `show()` 函数中所示。 另一种解决义性问题的方法是使用虚继承(Virtual Inheritance),它可以确保在多重继承中只有一个共享的基类实例,从而消除了义性问题。通过在继承链中的虚基类前添加关键字 `virtual`,可以声明虚继承。使用虚继承后,派生类会直接访问虚基类的成员,而不会出现义性。 总结起来,多重继承中的义性问题是指当派生类同时继承自多个具有相同成员函数或成员变量的基类时,可能会导致成员访问的义性。可以通过作用域解析运算符和虚继承来解决这个问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值