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

C++学习笔记----9、发现继承的技巧(三)---- 尊重父类(1)

        当写继承类的时候,需要清楚父类与子类之间的交互。像生成顺序,构造函数链,以及转化都可以是问题的根源。

1、父类构造函数

        对象不会马上就能干活;它们必须由父类以及所包含的任意对象进行构建。c++定义了如下的生成顺序:

  1. 、如果类有基类,基类的缺省构造函数先执行,除非在构造函数生成器中有对基类的调用,在这种情况下,生成器先调用而不是缺省构造函数。
  2. 、类的Non-static数据成员以它们声明的顺序构建。
  3. 、类的构造函数体执行。

        这些规则可以递归应用。如果类有一个祖父类,祖父类先于父类初始化,等等。下面的代码展示了这种生成次序。该代码正确执行的输出为123。

import std;using namespace std;class Something
{
public:Something() { print("2"); }
};class Base
{
public:Base() { print("1"); }
};class Derived : public Base
{
public:Derived() { print("3"); }private:Something m_dataMember;
};int main()
{Derived myDerived;
}

        当myDerived对象生成的时候,Base的构造函数先调用,输出字符串“1”。接下来,m_dataMember初始化,调用Something构造函数,它输出字符串“2”。最后,Derived构造函数调用,输出“3”。

        注意Base的构造函数是自动调用的。c++自动调用父类有的缺省构造函数。如果父类没有缺省构造函数或者有但是你想用另外的父类构造函数,你可以在构造函数初始化器内初始化数据成员时chain构造函数。例如,下面的代码展示了Base的缺省缺省构造函数的版本。Derived的相关版本必须显式告诉编译器如何 调用Base构造函数,要么代码就会编译失败。

class Base
{
public:explicit Base(int i) {}
};class Derived : public Base
{
public:Derived() : Base { 7 } { /* Other Derived's initialization ... */ }
};

        Derived构造函数传了一个固定值(7)给Base构造函数。当然了,Derived也可以传递一个变量:

Derived::Derived(int i) : Base { i } { /* Other Derived's initialization ... */ }

        从继承类中传递构造函数变量给基类是完全没有问题的,也是很正常的。然则传递数据成员是不行的。代码编译没有问题,但是记住数据成员是在基类构建完成之后才会初始化的。如果作为参数传递一个数据成员给父类构造函数,它是没有被初始化的。

2、父类析构函数

        因为析构函数不接受参数,编程语言会自动调用父类的析构函数。析构函数的调用次序与构造函数的次序刚好相反:

  1. 、调用类的析构函数体。
  2. 、类的数据成员以构建相反的次序依次失效。
  3. 、如果有父类的话,被析构。

        再次声明,该规则可以递归。链条最低的成员首先失效。下面的代码给上面的例子添加了析构函数。析构函数都声明成了virtual!执行的话,代码输出“123321”。

class Something
{
public:Something() { print("2"); }virtual ~Something() { print("2"); }
};class Base
{
public:Base() { print("1"); }virtual ~Base() { print("1"); }
};class Derived : public Base
{
public:Derived() { print("3"); }virtual ~Derived() override { print("3"); }private:Something m_dataMember;
};

        如果上面的析构函数不是被声明为virtual,代码看起来也能正常运行。然而,如果 调用Base指针上的delete,而该指针是指向一个Derived实例的话,析构链条就不对了。例如,如果将上面代码的所有析构函数的virtual与override关键字移除的话,当Derived对象作为一个指向Base的对象来访问并且删除的话就会有问题出现了,如下所示:

Base* ptr { new Derived{} };
delete ptr;

        该代码的输出会是令人吃惊的“1231”。当ptr指针删除时,只有Base析构函数被调用,因为该析构函数没有被声明为virtual。作为结果,Derived析构函数没有被调用,其数据成员的析构函数也没有被调用!

        从技术上来说,改正前面的问题只需要将Base的析构函数标记为virtual。其”virtualness”属性自动应用于任何它的继承类。然而,我强烈建议你显式将所有的析构函数标记为virtual,这样就永远不需要担心这方面的问题。

        警告:总是把析构函数标记为virtual!编译器生成的缺省析构函数不是virtual,所以应该定义(或显式缺省)一个virtual析构函数,至少对于所有的non-final基类。

3、在构造函数也析构函数中调用virtual成员函数

        在构造函数与析构函数中virtual成员函数表现是不一样的。如果继承类一从基类重载来的virtual成员函数,从基类构造函数或析构函数调用该成员函数调用的是基类的virtual成员函数而不是继承类中的重载版本!换名话说,在构造函数或析构函数中对virtual成员函数的调用是在编译时静态解析的。

        构造函数的这种行为的原因与构建一个继承类的实例时的初始化顺序相关。当生成这样的实例时,任何基类的构造函数先调用,在继承类实例整体初始化之前。因此,去调用还没有初始化的继承类中重载的virtual成员函数是危险的。对于析构函数也是类似的原因,也是当析构一个对象时其析构的顺序。

        如果确实需要对构造函数进行行为多态化,虽然不是很推荐,也是可以在基类中定义一个initialize() virtual成员函数的,它可以被继承类重载。客户生成类实例,在构建完成后一定要调用这个initialize()成员函数。

        同样的,如果需要在析构函数中使行为多态化,再次强调,是不推荐的,可以定义一个shutdown() virtual成员函数,对象在析构之前需要客户去调用这个函数。

http://www.lryc.cn/news/463302.html

相关文章:

  • 启动service报错ORA-44317: database open read-only
  • GNU/Linux - Savannah项目
  • Debug-028-el-carousel走马灯-当展示图片为2的问题处理
  • TapData 知识库 | 一文吃透数据整合(Data Consolidation)
  • MySQL数据的导出
  • 微服务--OpenFeign【重点】
  • 【力扣打卡系列】滑动窗口与双指针(两数之和)
  • 蚂蚁华东师范大学:从零开始学习定义和解决一般优化问题LLMOPT
  • 价格游戏的终章:品牌如何在通货膨胀时代智取市场
  • CVTE Android面试题及参考答案
  • Docker实战:从入门到进阶
  • Jupyter Notebook汉化(中文版)
  • C#的小数位保留以及四舍五入
  • KNNImputer
  • RHCE例行性工作笔记
  • ros2 action server示例、拓展、练习
  • 【Go语言】安装及使用基础教程
  • 【大模型】3分钟了解提示(Prompt)工程、检索增强(RAG)和微调
  • 太速科技-509-基于XCVU13P的4路QSFP28光纤PCIeX16收发卡
  • C#从零开始学习(基本语法概念)(2)
  • 基于SSM+微信小程序的家庭记账本管理系统(家庭1)
  • MEMC功能详解
  • C++ | Leetcode C++题解之第493题翻转对
  • Git 修改分支名
  • [自动化测试:Selenium]:环境部署和Webdriver的使用
  • 51单片机——OLED显示图片
  • Gin 协程mysql客户端
  • 量子门电路开销——T门、clifford门、toffoli门、fredkin门
  • C++之《剑指offer》学习记录(1):类型转换关键字
  • 【Linux】平台设备驱动