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

一文详解 C++ 继承体系

继承是 C++ 面向对象的核心机制之一,用来复用代码、表达“is-a”关系并支持运行时多态(virtual functions)。但 C++ 的继承体系很强大也很复杂:有访问控制(public/protected/private)、单继承/多继承、虚继承、虚函数表(vptr/vtable)、对象切片(slicing)、构造/析构顺序、name hiding、以及模板技巧(CRTP)等。弄清这些细节能避免常见的 BUG 和运行时错误。

优惠:https://secret-thanks.com/bi3/VX0.PC3Bp_vyb/mSVLJjZBDI0z2mMWDbQlwINQDLAd3LL/TTYdwhN/DbA-0IM_DJgi

基本语法与访问控制

struct Base {
public:int x;
protected:int p;
private:int q;virtual void vf() {}
};struct Derived : public Base {// public 继承:Base 的 public -> public, protected -> protected
};struct Derived2 : protected Base {// protected 继承:Base 的 public/protected -> protected 在 Derived2 中
};struct Derived3 : private Base {// private 继承:Base 的 public/protected -> private 在 Derived3 中
};
  • public 继承:表示 Derived is-a Base,允许外部把 Derived* 隐式转换为 Base*,常用的“接口继承”方式。
  • protected/private 继承:更像是“实现继承 / 用于实现”的手段(“implemented-in-terms-of”),外部不能隐式把 Derived 转为 Base(或受限制)。极少用于 API 设计,常用于库内部实现细节。
  • struct 默认继承/成员访问是 publicclass 默认是 private

成员继承与名字隐藏(name hiding)

  • 派生类继承基类的非 private 成员(数据、函数、类型别名等),但访问控制仍然受继承类型影响。
  • 名字隐藏:如果派生类定义了与基类同名函数(即使签名不同),基类中同名的所有重载都会被隐藏。要引入基类的重载,使用 using
struct Base { void f(int); void f(double); };
struct Derived : Base {using Base::f; // 引入基类的 f 重载集合void f(char);  // 新增一个重载
};

构造 / 析构 顺序(重要)

构造顺序(most-derived -> bases? careful!):

  1. 虚基类(virtual bases)先被构造(只构造一次),顺序按照最派生类继承声明的某个规则(通常是从左到右和声明顺序,细节由标准定义)。
  2. 然后按派生类 base-specifier-list 中从左到右顺序构造非虚基类
  3. 再构造派生类的成员(按在类中声明的顺序)。
  4. 最后进入派生类构造函数体(body)。

析构顺序与之相反(派生析构体先运行,然后成员,然后 base,最后虚 base)。

注意:对于虚继承,最派生类负责调用虚基类的构造函数并传参(即虚基由 most-derived 初始化)。

对象布局、vptr/vtable、对象切片

  • 为了实现动态绑定(virtual functions),编译器通常在每个多态(含虚函数)的对象中放一个指向该类型 vtable 的指针(称为 vptr)。vtable 存放函数指针用于动态调用。
  • 多重/虚继承时,可能存在多个 vptr 或额外的指针(如 vbptr)用于基类偏移调整,具体实现依编译器而异。
  • 对象切片(slicing):把派生类对象按值赋给基类对象会丢失派生部分:
struct Base { virtual ~Base() = default; int b; };
struct Derived : Base { int d; };Derived dd; Base b = dd; // slicing:b 不包含 d

因此要传递多态对象应使用指针或引用(Base*/Base& 或 智能指针)。

虚函数与运行时多态

struct Base {virtual void foo() { std::cout << "Base\n"; }virtual ~Base() = default; // 若要通过 Base* 删除派生对象,必须 virtual
};struct Derived : Base {void foo() override { std::cout << "Derived\n"; }
};
  • virtual 声明使得调用 ptr->foo() 在运行时按对象的动态类型选择实现(动态绑定)。
  • override(C++11)用于标注:编译器会检查该函数确实覆盖了基类的虚函数(避免写错签名后非覆盖导致的问题)。
  • final 可以阻止进一步重写:void foo() final;
  • 虚析构:如基类打算作为多态基类,必须有虚析构函数,否则通过 Base* 删除 Derived* 会导致未定义行为(派生析构未被调用)。

纯虚函数与抽象类(interface)

struct Interface {virtual void f() = 0; // 纯虚函数virtual ~Interface() = default;
};
  • 有纯虚函数的类是抽象类,不能实例化。
  • 抽象类常用作接口(纯虚 + 无数据成员)。
  • 纯虚函数也可以有实现(少见),但仍使类抽象:virtual void f() = 0; 在类外定义 void Interface::f(){...}

协变返回(Covariant return types)

派生类重写虚函数时允许返回协变类型(返回指针或引用时):

struct Base { virtual Base* clone() const; };
struct Derived : Base { Derived* clone() const override; } // 合法:Derived* 是 Base* 的协变类型

多重继承(multiple inheritance)

C++ 允许从多个基类派生:

struct A { int a; virtual ~A()=default; };
struct B : A {};
struct C : A {};
struct D : B, C {}; // D 中存在两个 A 子对象(如果不是虚继承)
  • 多重继承带来二义性D d; d.a; 会编译错误(不确定访问哪个 A::a),必须显式 d.B::ad.C::a
  • 如果基类都是多态(含 virtual),并想要共享同一个基类子对象(避免重复),则用 虚继承(virtual base)。

虚继承(diamond / 菱形问题)

经典菱形:

   A/ \B   C\ /D

如果 B、C 都继承自 A,而 D 又从 B、C 继承,默认会有两个 A 子对象。若想在 D 中只保留一个 A 子对象,B 和 C 应该虚继承 A:

struct A { int a; };
struct B : virtual A {};
struct C : virtual A {};
struct D : B, C {}; // 只有一个 A 子对象,由 D 初始化
  • 初始化:最派生类 D 负责初始化虚基 A(传递构造参数)。B、C 不再单独构造 A。
  • 代价:虚继承会引入额外的实现复杂度(编译器可能增加偏移表或指针来在运行时找到虚基),导致对象体积或访问成本上升。具体代价依实现而异(但通常是存在的)。

超低价esim流量卡:https://www.wanmoon.mom/redteago-esim/

指针/引用转换、dynamic_cast 与 RTTI

  • static_cast<T*>:在继承链上做编译时转换(不做运行时检查)。
  • dynamic_cast<T*>:在多态基类上做安全向下转换(需要包含虚函数以启用 RTTI)。如果失败,返回 nullptr(指针情况);引用失败抛 std::bad_cast
Base* b = ...;
Derived* d = dynamic_cast<Derived*>(b);
if (d) { /* 成功 */ }
  • typeid:检查对象的动态类型(多态类型时返回动态类型),需注意 typeid(*ptr) 要保证 ptr 非空。

名称查找 / overload resolution 的细微点

  • 名称查找先在派生类进行,若找到对应名称则基类中同名会被隐藏(无论签名),接着在该名字对应的可用集合中做重载解析。
  • 解决隐藏常用方法:using Base::f; 或者显式 Base::f() 调用。

空基优化(EBO:Empty Base Optimization)

  • 如果基类是空类(没有非静态数据成员),编译器通常会把它作为空子对象并不增加派生对象的大小(EBO),这在如 std::tuple 等模板库里很常见,用以节省空间。
  • 但如果派生类有多个同类型的空基,标准允许但具体细节和符号链接(type identity)有关;一般情况下 EBO 会被使用。

模板技巧:CRTP(静态多态)

CRTP(Curiously Recurring Template Pattern)是一种静态多态替代运行时虚函数的技巧:

template<typename Derived>
struct BaseCRTP {void interface() { static_cast<Derived*>(this)->implementation(); }
};struct Impl : BaseCRTP<Impl> {void implementation() { /* ... */ }
};

优点:无虚拟表开销(编译期分派),适合性能敏感场景;缺点:不能在运行时基类指针上统一操作(不是运行时多态)。

常见坑与问答(FAQ)

  • Q:覆写没有被调用?
    A:可能没有把基类函数声明为 virtual,或发生了切片(by-value),或签名不同(const/ref/参数/返回类型不一致导致并非覆盖)。用 override 可快速发现签名问题。
  • Q:为什么 delete base_ptr 导致内存泄漏/未定义行为?
    A:基类析构函数不是 virtual,导致派生析构未被调用。多态基类应有虚析构。
  • Q:什么时候用虚继承?
    A:只有在确实需要解决菱形重复基类子对象时使用;它有运行时成本,通常尽量避免复杂的多重继承设计。
  • Q:继承还是组合?
    A:如果是“is-a” 用继承;如果只是“has-a” 或复用实现首选组合(成员持有)—组合更灵活、耦合更低。
  • Q:如何安全下转(downcast)?
    A:使用 dynamic_cast(带多态基类)或设计更清晰的 API(避免运行时检查),谨慎使用 static_cast

实战示例(菱形 + 构造参数)

#include <iostream>struct A {A(int v) : x(v) { std::cout<<"A("<<x<<")\n"; }int x;
};struct B : virtual A {B(int v) : A(v) {} // 无效?B 不能单独决定虚基的初始化(取决于 most-derived)
};struct C : virtual A {C(int v) : A(v) {}
};struct D : B, C {D(int v) : A(v), B(v), C(v) { // most-derived D 必须显式初始化虚基 Astd::cout<<"D\n";}
};int main(){D d(42);std::cout<<d.x<<"\n";
}

输出会显示 A(42) 只被构造一次(由 D 初始化),然后 D 的构造继续。

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

相关文章:

  • AI_RAG
  • 本地连接跳板机
  • 10. 怎么实现深拷贝?
  • ABP VNext + Apache Kafka Exactly-Once 语义:金融级消息一致性实战
  • VSCode添加Python、Java注释技巧、模板
  • 笔试——Day33
  • java web项目入门了解
  • 微信原生小程序 Timeline 组件实现
  • 在Word和WPS文字中快速拆分、合并表格
  • JavaWeb03——javascript基础语法
  • C++-AVL树
  • 微软将于 10 月停止混合 Exchange 中的共享 EWS 访问
  • SOLi-LABS Page-3 (Stacked injections) --39-53关
  • 使用 Vuepress + GitHub Pages 搭建项目文档(2)- 使用 GitHub Actions 工作流自动部署
  • 如何解决 Vue 项目启动时出现的 “No such module: http_parser” 错误问题
  • 2G内存的服务器用宝塔安装php的fileinfo拓展时总是卡死无法安装成功的解决办法
  • 企业级web应用服务器TOMCAT入门详解
  • kettle插件-kettle MinIO插件,轻松解决文件上传到MinIO服务器
  • 解决本地连接服务器ollama的错误
  • 大语言模型提示工程与应用:大语言模型对抗性提示安全防御指南
  • LLVM编译器入门
  • Java基础-TCP通信单服务器接受多客户端
  • 关于开发语言的一些效率 从堆栈角度理解一部分c java go python
  • 软考 系统架构设计师系列知识点之杂项集萃(119)
  • 数据结构(9)——排序
  • QT第三讲- 机制、宏、类库模块
  • 数字图像处理基础——opencv库(Python)
  • 算法_python_牛客华为机试笔记_01
  • 【Python 高频 API 速学 ③】
  • RecyclerView 中 ViewHolder