当前位置: 首页 > article >正文

C++学习笔记----9、发现继承的技巧(六)---- 有趣且令人迷惑的继承问题(7)

6、非公共继承

        在前面所有的例子中,父类总是用public关键字列出。你可能会想是否父类也可以是private或protected。实际上,是可以的,但都不像public一样常见。如果不对父类进行访问指示符的指定,对于类来讲就是private继承,对于struct是public继承。

        将与父类的关系声明为protected意味着从基类来的public成员函数与数据成员变为了继承类中的protected。类似地,指定为private继承意味着基类的所有public与protected成员函数与数据成员在继承类中会变成private。

        可能想要统一地对父类的访问层次用这种方式进行降级有几个原因,但大部分原因是层次结构设计的瑕疵。有些程序员滥用这个语言特性,常见于多重继承的组合,应用了类的“部件”。不是使Airplane类包含一个引擎数据成员与一个机身数据成员,反而是使Airplane类是一个protected引擎与一个protected机身。用这种方式,对客户端代码来讲,Airplane既不像引擎,也不像机身(因为所有的东东都是protected),但是可以内部使用所有这些功能。

        注意:如果没有其他原因,只是不熟悉的话,非公共继承很少见,推荐谨慎使用。

7、虚基类

        在本章的前面部分,学到了不明确的基类,当多重父类每个都有一个通用属性的父类时会加剧这种不明确,如下图所示:

        早期的推荐方案是确保共享的父类没有自身的任何功能。那样的话,其成员函数永远不会被调用,就不会有不明确的问题。

        C++有另外的方法,叫做虚基类,来解决这种问题,如果你确实想让共享父类有自身的功能的话。如果共享父类被标记为虑基类,就不会有不明确性。下面的代码添加了一个sleep()成员函数,包含了一个实现,对于Animal基类来说,修改了Dog与Bird类,作为虚基类继承了Animal。在不用虚基类的情况下,在DogBird对象上调用sleep()会是不明确的,因为DogBird有两个Animal的子对象,一个来自于Dog,一个来自于Bird,所以会产生编译器错误。然而,当Animal是虚继承时,DogBird只有一个Animal的子对象,所以调用sleep()时不会有不明确性。

import std;

using namespace std;

class Animal
{
public:
	virtual void eat() = 0;
	virtual void sleep() { println("zzzzz...."); }
};

class Dog : public virtual Animal
{
public:
	virtual void bark() { println("Woof!"); }
	void eat() override { println("The dog ate."); }
};

class Bird : public virtual Animal
{
public:
	virtual void chirp() { println("Chirp!"); }
	void eat() override { println("The bird ate."); }
};

class DogBird : public Dog, public Bird
{
public:
	void eat() override { Dog::eat(); }
};

int main()
{
	DogBird myConfusedAnimal;
	myConfusedAnimal.sleep();  // Not ambiguous because of virtual base class
}

        在这种类层次结构中要注意构造函数。例如,下面的代码添加了一些不同类的数据成员,添加了构造函数来初始化这些数据成员,添加了Animal的缺省构造函数,原因会在代码之后进行解释。

class Animal
{
public:
	explicit Animal(double weight) : m_weight{ weight } {}
	virtual double getWeight() const { return m_weight; }
protected:
	Animal() = default;
private:
	double m_weight{ 0.0 };
};
	
class Dog : public virtual Animal
{
public:
	explicit Dog(double weight, string name) : Animal{ weight }, m_name{ move(name) } {}
private:
	string m_name;
};
	
class Bird : public virtual Animal
{
public:
	explicit Bird(double weight, bool canFly) : Animal{ weight }, m_canFly{ canFly } {}
private:
	bool m_canFly{ false };
};
	
class DogBird : public Dog, public Bird
{
public:
	explicit DogBird(double weight, string name, bool canFly)
		: Dog{ weight, move(name) }, Bird{ weight, canFly } {}
};

int main()
{
	DogBird dogBird{ 22.33, "Bella", true };
	println("Weight: {}", dogBird.getWeight());
}

        当运行这段代码时,结果并不是预想的那样:

Weight: 0

        看起来给定的22.33的重量在main()函数中的DogBird构造时丢失掉了。怎么回事?这段代码使用了虚Animal基类;因此,DogBird实例只有一个Animal子对象。DogBird构造函数调用了Dog与Bird的构造函数,都指向了Animal基类的构造函数。这就意味着Animal被构造了两次。这是不允许的。在这种情况下,当从继承类的构造函数中调用时,编译器使对Dog与Bird构造函数中的对Animal构造函数的调用失效,取而代之的是调用了Animal基类的缺省构造函数,这样就需要Animal的protected缺省构造函数。所有这些意味着大部分继承类自身要对共享类的构造函数调用负责。正确的实现如下:

class Animal
{
public:
	explicit Animal(double weight) : m_weight{ weight } {}
	virtual double getWeight() const { return m_weight; }
protected:
	Animal() = default;
private:
	double m_weight{ 0.0 };
};

class Dog : public virtual Animal
{
public:
	explicit Dog(double weight, string name) : Animal{ weight }, m_name{ move(name) } {}
protected:
	explicit Dog(string name) : m_name{ move(name) } {}
private:
	string m_name;
};

class Bird : public virtual Animal
{
public:
	explicit Bird(double weight, bool canFly) : Animal{ weight }, m_canFly{ canFly } {}
protected:
	explicit Bird(bool canFly) : m_canFly{ canFly } {}
private:
	bool m_canFly{ false };
};

class DogBird : public Dog, public Bird
{
public:
	explicit DogBird(double weight, string name, bool canFly)
		: Animal { weight }, Dog{ move(name) }, Bird{ canFly } {}
};

int main()
{
	DogBird dogBird{ 22.33, "Bella", true };
	println("Weight: {}", dogBird.getWeight());
}

        在这个实现中,给Dog与Bird添加了protected单参数构造函数。由于只用于继承类所以是protected。客户端代码只用两个参数的构造函数来构造Dog与Bird。

        在做了这些修改后,输出正确:

Weight: 22.33

        注意:在类层次结构中虚基类是避免不明确性的一个伟大的方式。唯一的缺点是许多c++程序员对这个概念还不是很熟悉。


http://www.kler.cn/a/376762.html

相关文章:

  • django的models使用介绍。
  • flutter ios ffi 调试 .a文件 debug可以 release 不行
  • c语言动态内存 :经典例题
  • 实战OpenCV之目标检测
  • 使用pathview在线渲染KEGG Pathway Map,给感兴趣的基因、化合物添加颜色
  • 力扣刷题(sql)--零散知识点(2)
  • 爬虫ip与反爬虫的“猫鼠游戏”
  • 萌熊数据科技:剑指脑机转入,开启科技新篇章
  • 机器学习实战:从数据准备到模型部署
  • 网关如何传递信息给微服务
  • 虚拟机安装Ubuntu系统
  • Kafka物理存储机制深度解析
  • 市场分化!汽车零部件「变天」
  • 《化学试剂》
  • linux8在线扩容/home目录
  • Redis中String 的底层实现是什么?
  • 读书笔记--类加载器
  • 深入理解网络协议:OSPF、VLAN、NAT与ACL详解
  • 学习正则表达式,如何校验手机号与电子邮箱
  • RabbitMQ替换默认端口
  • C语言实验 选择结构
  • C++之“取地址运算符重载”
  • 从0开始的STM32之旅 7 串口通信(I)
  • idea 配置tomcat 服务
  • SpringBoot抗疫物资管理:系统开发与部署
  • 2024/11/2 安卓创建首页界面