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

【语法】C++继承中遇到的问题及解决方法

目录

1.子类构造函数中初始化父类成员

2.子类显式调用父类的析构函数

第一种说法:重定义

反驳:

第二种说法:operator~

3.因编译器版本过低而出现错误


贴主在学习C++的继承时,遇到了很多问题,觉得很变态,特此发帖分享

1.子类构造函数中初始化父类成员

众所周知,在子类的构造函数中是不能初始化父类成员的,必须调用父类的构造函数来完成初始化

#include <iostream>
using namespace std;
class base
{
public:base():_a(0){cout << "constructor base \n";}
protected:int _a;
};class derived : public base
{
public:derived():_a(0)//报错,因为_a是父类成员{cout << "constructor derived \n";}
};

 但有一种情况可能会被误认为是初始化

#include <iostream>
using namespace std;
class base
{
public:base():_a(0){cout << "constructor base \n";}
protected:int _a;
};class derived : public base
{
public:derived()//:base()这里已经隐式调用了base的构造函数了{_a = 0;cout << "constructor derived \n";}
};

这确实可以运行,但这里其实并不是初始化(initialization),而是赋值(assignment),这里看似没有调用父类的初始化,但其实会隐式调用构造函数

可以看到,base的构造函数还是被调用了,这就是隐式调用了base的构造函数 

那拷贝构造函数呢?若子类不定义默认构造函数的话,拷贝构造时确实会调用父类的拷贝构造函数完成父类函数的初始化

#include <iostream>
using namespace std;
class base
{
public:base():_a(0){cout << "constructor base \n";}base(const base& b):_a(b._a){cout << "copy constructor base \n";}
protected:int _a;
};class derived : public base
{
public:derived():base(){cout << "constructor derived \n";}
};int main()
{derived a;cout << "\n";derived b(a);return 0;
}

输出结果:

 可以看到在拷贝构造时,隐式调用了父类的拷贝构造

但如果子类有用户定义的拷贝构造函数,如果没有显式调用父类的拷贝构造函数,编译器就会隐式调用父类的构造函数

#include <iostream>
using namespace std;
class base
{
public:base():_a(0){cout << "constructor base \n";}base(const base& b):_a(b._a){cout << "copy constructor base \n";}
protected:int _a;
};class derived : public base
{
public:derived():base(){cout << "constructor derived \n";}derived(const derived& d)//子类拷贝构造不显式调用父类的拷贝构造,会隐式调用父类的构造函数完成父类成员初始化{cout << "copy constructor derived \n";}
};int main()
{derived a;cout << "\n";derived b(a);return 0;
}

输出结果:

可以看到,在拷贝构造时,编译器先隐式调用了父类的构造函数

2.子类显式调用父类的析构函数

众所周知,子类的析构函数会在被调用完成后自动调用父类的析构函数清理父类成员。因为这样才能保证子类对象先清理子类成员再清理父类成员的顺序。

#include <iostream>
using namespace std;
class base
{
public:base(){cout << "constructor base \n";}~base(){cout << "destructor base \n";}
};class derived : public base
{
public:derived(){cout << "constructor derived \n";}~derived(){~base();cout << "destructor derived \n";}//会报错
};int main()
{derived a;return 0;
}

报错: 

当然,对于99%的情况来说,根本用不到显式调用析构函数,剩下的1%就是placement new 管理内存 的情况

对于这里的报错,我目前知道两种说法

第一种说法:重定义

这里的~base和~derived构成重定义(隐藏),但他们两个函数名不相同啊?这是因为经过编译器处理后所有的析构函数都会被处理成destructor(为了支持多态),那他们两个析构函数就是重名函数了,父类的析构函数就会被重定义,此时要想让编译器知道你要调用的是父类的析构,就要在前面加上作用域限定符

int main()
{cout << int() <<endl;return 0;
}

反驳:

“隐藏”是非限定名查找的一部分。而析构函数的查找方式有所不同,因此不存在隐藏关系。也没有所谓的“转换为特殊名称 destructor(析构函数)”这样的过程作为名称查找的目的。 

如果有大佬知道到底对不对的,欢迎评论区解答!!!

第二种说法:operator~

这里的报错和是不是继承没有关系,因为下面代码也会报同样的错误

class base
{~base(){cout << "~base \n";}void func(){~base();}
};

这段代码的执行顺序其实是先base(),再~

base()会先构造一个base的临时对象。这里其实不难理解,就例如int(),会调用int的默认构造函数创建临时的int变量,它的值是0

int main()
{cout << int() <<endl;return 0;
}

输出:0

所以base()也一样,会调用base的默认构造函数来构建一个临时的base对象

然后会试图调用base对象的operator~重载,如下图

#include <iostream>
using namespace std;
class base
{
public:base(){cout << "constructor base \n";}~base(){cout << "destructor base \n";}void operator~(){cout << "operator ~ \n";}
};class derived : public base
{
public:derived(){cout << "constructor derived \n";}~derived(){base::~base();cout << "destructor derived \n";}
};
int main()
{~base();cout <<"\n";base().operator~();//第一行就相当于这么调用return 0;
}

输出结果:

如上图,第一个和第一行和第三行的代码输出结果相同

所以报错的原因就是base类没有operator~重载

流程:先构造一个临时的base对象,再调用临时base对象的operator~运算符,最后析构这个临时的base对象

3.因编译器版本过低而出现错误

 根据第二个问题可以得知,当写出~base()时,会先构造一个临时的base对象,然后调用该对象的operator~操作符,最后析构这个临时base对象

而如果要直接在子类中调用父类析构函数,可以这样

class derived : public base
{
public:derived(){cout << "constructor derived \n";}~derived(){base::~base();cout << "destructor derived \n";}
};

~base()的前面加上作用域限定符,这样编译器就知道你是要调用父类的析构函数,而不是operator~重载。

但如果在父类有operator~重载时,在子类中调用base::~base();呢?

#include <iostream>using namespace std;
class base
{
public:base(){cout << "constructor base \n";}~base(){cout << "destructor base \n";}void operator~(){cout << "operator ~ \n";}
};class derived : public base
{
public:derived(){cout << "constructor derived \n";}~derived(){base::~base();cout << "destructor derived \n";}
};
int main()
{derived d;return 0;
}

上面代码在我之前的编译器中,就会编译错误

error:

cmd /c chcp 65001>nul && C:\mingw64\bin\g++.exe -fdiagnostics-color=always -g "D:\Valkyrie-text\simple text\text.cpp" -o "D:\Valkyrie-text\simple text\text.exe"
D:\Valkyrie-text\simple text\text.cpp: In destructor 'derived::~derived()':
D:\Valkyrie-text\simple text\text.cpp:15:28: error: no matching function for call to 'derived::~derived()'~derived(){base::~base();cout << "destructor derived \n";}^
D:\Valkyrie-text\simple text\text.cpp:7:5: note: candidate: 'base::~base()'~base(){cout << "destructor base \n";}^
D:\Valkyrie-text\simple text\text.cpp:7:5: note:   candidate expects 1 argument, 0 provided

后来发现,这是编译器在抱怨缺少参数

在前面加上thts->就可以了

this->base::~base();

相信又很多人运行上面代码时都会报和我一样的错误,这是因为编译器的版本太低

我之前的编译器是8.1.0的gcc

现在更新了,用14.2.0的版本就不会再报这个错了,所以强烈建议大家更新一下自己的编译器!

相关文章:

  • E2E 测试
  • JavaScript 相关知识点整理
  • C++ 红黑树
  • 【Vagrant+VirtualBox创建自动化虚拟环境】Ansible测试Playbook
  • git fetch和git pull的区别
  • ​【空间数据分析】缓冲区分析--泰森多边形(Voronoi Diagram)-arcgis操作
  • Vue使用Sortablejs拖拽排序 视图显示与数据不一致、拖拽结束后回跳问题
  • excel如何做相关系数分析
  • 【网络原理】TCP异常处理(二):连接异常
  • 脑机接口:重塑人类未来的神经增强革命
  • HarmonyOS NEXT 诗词元服务项目开发上架全流程实战(二、元服务与应用APP签名打包步骤详解)
  • 什么是 MCP?AI 应用的“USB-C”标准接口详解
  • CentOS环境下搭建seata(二进制、MySQL)
  • [计算机网络]物理层
  • Nginx核心功能与LNMP部署
  • 主流微前端框架比较
  • pytest-前后置及fixture运用
  • Mybatis-plus代码生成器的创建使用与详细解释
  • Nginx部署与源码编译构建LAMP
  • SVMSPro平台获取Websocket视频流规则
  • “人工智能是年轻的事业,也是年轻人的事业”,沪上高校师生畅谈感想
  • 建行一季度净利833.51亿同比下降3.99%,营收降5.4%
  • 逛了6个小时的上海车展。有些不太成熟的感受。与你分享。
  • 阿里千问3系列发布并开源:称成本大幅下降,性能超越DeepSeek-R1
  • 国家统计局:一季度规模以上工业企业利润延续持续恢复态势
  • 蜀道考古调查阶段性成果发布,新发现文物遗存297处