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

C++中不要重新定义继承而来的non-virtual函数

在 C++ 中,重定义继承而来的 non-virtual(非虚)函数通常是不推荐的,原因如下:

  1. 隐藏父类的实现:如果在派生类中重定义了一个非虚函数,这将隐藏父类中具有相同名称和参数的函数。这意味着即使通过基类的指针或引用调用该函数,也会调用派生类中的版本,这可能违反了多态性原则,导致无法调用预期的基类函数。

  2. 破坏封装:重定义非虚函数可能会导致派生类与基类之间的耦合增强,因为派生类需要知道基类的具体实现细节,并且在派生类中复制这些细节。这违反了封装的原则,使得代码难以维护和理解。

  3. 意图不明确:在派生类中重定义非虚函数可能会使类的设计意图变得不清晰。虚函数表明存在意图进行重写(override),而非虚函数通常表示不打算被重写。如果需要在派生类中提供不同的行为,应该使用虚函数。

  4. 多态行为的缺失:非虚函数不支持运行时多态,这意味着即便派生类中有与基类同名的函数,通过基类指针或引用调用该函数时,总是会调用基类的实现,而不是派生类的实现。这可能导致派生类的行为不符合预期。

  5. 维护难度增加:如果在派生类中重定义了基类的非虚函数,任何对基类函数的修改都需要在所有派生类中进行检查和相应的修改,这增加了代码的维护难度。

  6. 引起混淆:重定义基类中的非虚函数可能会使得阅读和理解代码的人员产生混淆,特别是在有多级继承时,理解哪个版本的函数被调用变得更加复杂。

如果确实需要在派生类中更改基类中某个函数的行为,应该考虑以下方案:

  • 将基类函数声明为虚函数(virtual),并在派生类中重写(override)它。
  • 如果不希望在基类中提供默认实现,可以将基类函数声明为纯虚函数(pure virtual)。
  • 如果派生类需要提供一个完全不同的函数,可以考虑给该函数取一个不同的名称,以避免与基类函数混淆。

让我们通过一个简单的例子来说明为什么不建议在派生类中重定义继承而来的 non-virtual 函数。

假设我们有一个基类 Base 和一个从 Base 继承的派生类 Derived。在基类中,我们有一个非虚拟函数 foo(),在派生类中我们重定义了这个函数。

#include <iostream>class Base {
public:void foo() {std::cout << "Base::foo()" << std::endl;}
};class Derived : public Base {
public:void foo() {std::cout << "Derived::foo()" << std::endl;}
};int main() {Base b;Derived d;// 直接调用b.foo(); // 输出: Base::foo()d.foo(); // 输出: Derived::foo()// 通过基类指针调用Base* ptr = &d;ptr->foo(); // 输出: Base::foo()return 0;
}

在上面的例子中,即使 ptr 指向的是 Derived 类型的对象,调用 ptr->foo() 时还是执行了 Base 类的 foo 方法,而不是 Derived 类的 foo 方法。这是因为 foo 被定义为非虚函数,不支持多态。

现在,假设我们想要实现多态行为,我们需要将 Base 类中的 foo() 方法声明为虚函数,并在派生类中重写它:

#include <iostream>class Base {
public:virtual void foo() {std::cout << "Base::foo()" << std::endl;}
};class Derived : public Base {
public:void foo() override { // 使用 override 关键字明确表示重写std::cout << "Derived::foo()" << std::endl;}
};int main() {Base b;Derived d;// 直接调用b.foo(); // 输出: Base::foo()d.foo(); // 输出: Derived::foo()// 通过基类指针调用Base* ptr = &d;ptr->foo(); // 输出: Derived::foo()return 0;
}

在这个修改后的例子中,由于 foo 现在是虚函数,并且在派生类中被重写,ptr->foo() 调用将执行 Derived 类的 foo 方法,展现了多态行为。

重定义可能会隐藏基类实现并导致预期外的行为,而将函数声明为虚函数并在派生类中重写可以实现正确的多态行为。

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

相关文章:

  • C++ 对象型参数和返回值
  • LeetCode 字符串专题——KMP算法_28. 找出字符串中第一个匹配项的下标
  • 上班不想用脑子写代码了怎么办?那就试试Baidu Comate啊宝贝
  • 【管理咨询宝藏94】某国际咨询公司供应链财务数字化转型方案
  • C++_使用邻接表(链表-指针)实现有向图[完整示例及解释]
  • Gitlab自动化测试的配置
  • Qwen-Audio:推动通用音频理解的统一大规模音频-语言模型(开源)
  • 杭州破冰之举:全面取消住房限购,激发市场新活力
  • ICode国际青少年编程竞赛- Python-1级训练场-变量练习
  • 学习STM32第二十天
  • 智能BI(后端)-- 系统异步化
  • AI绘画Stable Diffusion 插件篇:智能标签提示词插件sd-danbooru-tags-upsampler
  • Android OpenMAX(六)OMXStore
  • Ubuntu下halcon软件的下载安装
  • 『ZJUBCA Collaboration』WTF Academy 赞助支持
  • Python开源工具库使用之运动姿势追踪库mediapipe
  • 【Android Studio】开启真机调试
  • CMakeLists.txt语法规则:部分常用命令说明四
  • 学习前端第三十二天(Rest 参数与 Spread 语法,变量作用域,闭包)
  • mysql从入门到起飞+面试基础题
  • 设计模式:命令模式
  • setinterval和settimeout区别在于
  • shell_结束进程脚本
  • GDPU unity游戏开发 碰撞器与触发器
  • IP地址定位技术在网络安全中的作用
  • R语言中,查看经安装的包,查看已经加载的包,查看特定包是否已经安装,安装包,更新包,卸载包
  • spring boot3单模块项目工程搭建-下(个人开发模板)
  • 精准清理 MongoDB 数据:删除集合的正确姿势
  • java 执行修改语句
  • 【Linux系统化学习】网络套接字(编写简单的UDP服务端和客户端)