C++ 高频面试题之二
大纲
基础面试题
C++ 中构造函数和析构函数可不可以是虚函数,为什么
构造函数不可以是虚函数,
- 因为在调用构造函数时,编译器尚未将对象的虚函数表(
vtable
)建立完整,也就是尚未将对象的虚函数表指针(vptr
)设为派生类的版本,无法实现多态。
- 因为在调用构造函数时,编译器尚未将对象的虚函数表(
析构函数可以是虚函数
- 在有继承时应该将析构函数设为虚函数(即虚析构函数),因为这样才能确保通过基类指针删除派生类对象时,能够正确调用派生类的析构函数,防止资源泄漏。
C++ 中构造函数和析构函数可不可以抛出异常,为什么
构造函数可以抛出异常,但不建议这样做
- 因为当构造函数抛出异常时,对象构造会中止,进入未完全构造的状态。此时,编译器会自动调用已成功构造的成员对象或基类的析构函数,而未构造完成的成员对象则不会调用其析构函数。
- 特别注意:如果在构造函数中手动分配了资源(如使用裸指针、
new
分配内存等),而在抛出异常时未手动释放这些资源,就会发生内存泄漏。因此,推荐使用 RAII(如智能指针、STL 容器)来管理资源,或者使用try-catch
在异常抛出后完成资源释放。
析构函数可以抛出异常,但强烈不建议这样做
- 因为如果析构函数在对象销毁过程中抛出异常,而此时程序正在处理另一个未捕获的异常,会导致双重异常。此时,C++ 运行时系统将调用
std::terminate()
,直接终止程序,导致崩溃。 - 因此,析构函数内部应捕获所有可能发生的异常,避免异常向外传播,可以使用
try-catch
包裹资源清理逻辑,并在catch
块中记录日志或执行降级处理。
- 因为如果析构函数在对象销毁过程中抛出异常,而此时程序正在处理另一个未捕获的异常,会导致双重异常。此时,C++ 运行时系统将调用
总结
- 构造函数可以抛异常,用于表示构造失败,但需确保资源安全,避免内存泄漏。
- 析构函数不应该抛异常,防止异常传播冲突(双重异常错误)导致程序崩溃,建议使用
try-catch
处理内部异常。
C++ 中局部变量存储在哪里
普通局部变量在栈上,static
局部变量在静态区,局部动态分配(new
)的变量在堆上。
类型 | 存储区域 | 备注 |
---|---|---|
普通局部变量 | 栈(Stack) | 函数调用时分配,函数返回时自动回收 |
const 修饰的局部变量 | 栈(Stack) | 只是不可修改,本质还是普通局部变量 |
static 修饰的局部变量 | 静态区(Data Segment) | 程序整个运行期间都存在,生命周期和程序一样长 |
局部动态分配的变量(如 new 出来的) | 堆(Heap) | 手动分配内存,手动释放内存,不跟函数栈生命周期同步 |
C++ 中拷贝构造函数为什么传引用不传值
在 C++ 中,拷贝构造函数必须传引用(通常是 const
引用),而不能传值,否则会导致无限递归调用拷贝构造函数,最终造成编译不通过或者程序崩溃。
- 传引用的话,不需要拷贝,只是引用原对象,不触发拷贝构造。
- 加
const
是为了支持传入const
对象,并防止在函数内部意外修改原对象。
C++ 中内联函数的普通函数的区别(反汇编的角度)
在 C++ 中,内联函数和普通函数的主要区别体现在函数调用开销上。从反汇编层面来看,两者区别很明显:
普通函数:
- 编译器会生成真实的函数调用指令,比如在 x86 平台上通常是
CALL
指令。 - 调用普通函数时,程序会:
- 将参数压入栈中(或者通过寄存器传递)。
- 保存当前执行位置(调用点地址)。
- 跳转到函数地址执行。
- 函数执行完后,通过
RET
指令返回调用点继续执行。
- 在反汇编中,可以看到明显的
CALL
和RET
指令。
- 编译器会生成真实的函数调用指令,比如在 x86 平台上通常是
内联函数:
- 编译器在调用内联函数的地方直接展开函数体代码,不会生成
CALL
指令。 - 也就是说,没有函数调用开销,代码像普通语句一样直接排列在调用点。
- 在反汇编中,内联函数对应的机器码是直接展开的,看不到
CALL
指令,而是看到函数体相关的操作指令(比如加法、赋值、比较等)。
- 编译器在调用内联函数的地方直接展开函数体代码,不会生成
内联函数的普通函数的区别总结
特性 | 普通函数 | 内联函数 |
---|---|---|
调用方式 | 生成 CALL 指令跳转 | 直接展开函数体 |
性能 | 有函数调用开销(压栈、跳转、返回等) | 无函数调用开销(更快) |
二进制体积 | 较小(代码复用) | 可能变大(每次调用都复制函数体) |
反汇编特征 | 明显看到 CALL 和 RET 汇编指令 | 没有 CALL 汇编指令,代码直接嵌入 |
C++ 中如何实现一个不可以被继承的类
在 C++ 中,要实现一个不能被继承的类(Final Class),有以下两种常见的方法:
(1) 使用 C++ 11 引入的
final
关键字- 在类声明后加上
final
,表示该类禁止被继承。 - 符合现代 C++ 规范,如果尝试继承,会在编译期直接报错。
- 在类声明后加上
(2) 声明构造函数或析构函数为
private
或protected
,并且不给友元访问- 将类的构造函数或析构函数声明为
private
或protected
,并且不开放友元访问。 - 如果尝试继承,因为子类构造或析构时无法访问基类的构造函数或析构函数。
- 这种方式会让实例化对象也变复杂(比如只能通过静态方法创建对象),适合一些单例模式或者控制构造的场景。
- 将类的构造函数或析构函数声明为
C++ 中什么是纯虚函数,为什么要有纯虚函数
什么是纯虚函数?
在 C++ 中,纯虚函数(Pure Virtual Function)是指在基类中声明,但不提供具体实现,强制派生类必须重写(Override)这个函数。
- 定义形式是使用
virtual
关键字,并在函数声明后面加上= 0
,比如virtual void func() = 0;
。 - 有至少一个纯虚函数的类叫做抽象类(Abstract Class),抽象类不能直接实例化对象(只能作为基类使用)。
为什么要有纯虚函数?
纯虚函数是 C++ 中实现 “接口抽象” 和 “强制子类重写” 的关键机制,是支撑多态性的基石。
- 强制子类去实现某些接口 / 行为,保证程序的设计一致性。
- 为多态(运行期多态)提供基础支持。
- 描述一种 “规范” 或者 “接口”,而不是具体实现。
提示
- 一个类即使只有一个纯虚函数,也是抽象类,不能被实例化。
- 子类如果不实现所有继承的纯虚函数,那么子类也是抽象类。
- 抽象类可以有成员变量和成员函数。
C++ 中虚函数表存放在哪里
虚函数表(vtable
)存放在程序的静态存储区,由编译器维护。拥有虚函数的类,其每个对象的内部都持有一个指向它的虚函数表指针(vptr
),用于实现运行时多态。
虚函数表详细介绍
当一个类中有虚函数时,编译器通常会为这个类生成一张虚函数表(vtable
)。vtable
本质上是存放在程序的静态存储区(.rodata
只读数据段或全局数据段)的一张表,而对象本身只保存一份指向 vtable
的指针,通常称为虚函数表指针(vptr
)。
虚函数表(
vtable
)- 每个拥有虚函数的类都对应一张
vtable
,通常是只读的,防止程序运行时被篡改。 vtable
是一张静态分配的表(通常存放在.rodata
只读数据段或全局数据段)。vtable
里面存放的是各个虚函数的实际函数地址(指针)。
- 每个拥有虚函数的类都对应一张
虚函数表指针(
vptr
)- 拥有虚函数的类,其每个对象的内部都隐藏了一个
vptr
指针成员(占 4 个字节),指向所属类的vtable
。 vptr
通常在构造函数中由编译器自动初始化。
- 拥有虚函数的类,其每个对象的内部都隐藏了一个
为什么要这么设计
- 快速实现多态:调用虚函数时,通过
vptr
可以快速找到函数地址,效率很高(只多一次指针间接访问)。 - 支持动态绑定:编译器在运行时根据实际对象类型,动态找到正确的函数实现。
- 快速实现多态:调用虚函数时,通过
C++ 中 const 和 static 的区别
const
保证变量或函数行为不可修改,static
控制变量或函数的存储周期和作用域,两者关注点完全不同,但可以组合使用。
特性 | const | static |
---|---|---|
关键字含义 | 表示常量,值不可修改 | 表示静态存储周期,生命周期贯穿整个程序运行 |
修饰的对象 | 变量、指针、函数参数,函数返回值、成员函数等 | 变量、成员变量、成员函数 |
生命周期 | 作用域结束时销毁(正常对象) | 程序开始时分配,结束时销毁 |
主要作用 | 保证只读、不被意外修改 | 保持数据共享、持久化 |
常用场景 | 保护只读数据、接口规范 | 跨函数 / 跨对象共享数据、缓存数据 |
C++ 中四种强制类型转换
类型转换 | 主要用途 | 安全性 | 是否运行时检查 | 典型应用场景 |
---|---|---|---|---|
const_cast | 用于增加或去除 const / volatile 属性 | 安全(只改修饰,不会改变对象本质) | 否 | 修改函数参数的常量性(如 API 不一致场景) |
static_cast | 用于基本类型转换、类层次间的安全转换 | 较安全(编译期类型安全的转换) | 否 | int 转 float 、父类和子类之间的指针 / 引用转换(向上转换无风险) |
dynamic_cast | 多态类型之间安全向下转换 | 很安全(失败时返回 nullptr 或抛异常) | 是 | 运行时会判断实际类型,实现多态安全转换 |
reinterpret_cast | 最暴力,用于无关类型之间的转换 | 不安全(可能导致未定义行为) | 否 | 指针和整数之间转换、 不同类型指针之间转换 |
C++ 中 deque 的底层原理
deque
叫做双端队列,它是通过多段小连续块(buffer
)组织起来的,配合中央控制器(map
)来管理,实现了头部和尾部快速插入和删除,并支持随机访问。deque
的底层结构是由分块连续数组和一个中央控制器(map
)组成- 元素不是存在一整块内存里,而是存在若干小块内存(称为
buffer
)。 - 每个
buffer
是一个固定大小的数组(比如 512 bytes 或 4096 bytes 之类的,根据元素大小决定)。 - 有一个叫
map
的数组,存了所有buffer
的指针。 map
自己是连续的小数组,支持随机访问和动态扩展。
- 元素不是存在一整块内存里,而是存在若干小块内存(称为
提示
deque
一种双向开口的连续线性空间(双端队列容器),底层的数据结构是支持动态开辟内存空间的二维数组。deque
本质上是由一段一段的定量连续空间(分段连续内存空间)构造而成,一旦有必要在deque
的头端或尾端增加新空间,便会配置一段新的定量连续空间,然后串接在整个deque
的头端或尾端。
C++ 中虚函数和多态
- 虚函数是指在基类中使用
virtual
关键字声明的成员函数,目的是为了允许派生类重写(重新定义)它,从而实现运行时的多态性。 - 多态指的是,当通过基类指针或引用调用虚函数时,程序能够在运行时根据实际派生类对象的类型,动态决定调用哪一个派生类中重写的函数。
- 虚函数使得同一接口在不同对象上展现出不同的行为,C++ 通过虚函数表(
vtable
)和虚函数表指针(vptr
)机制来支持这种动态绑定。
C++ 中异常处理机制
异常处理机制
- C++ 中的异常机制主要通过
try-catch-throw
关键字组合来实现,用于处理程序运行时出现的错误。 - 当程序执行中遇到异常时,通过
throw
抛出一个异常对象,异常对象可以是内置类型、类对象或指针等。 - 异常抛出后,程序会沿调用栈向上寻找匹配的
catch
语句块进行处理。 - 如果找到了合适的
catch
,就进入处理代码块;如果没有找到匹配的处理器,程序会调用std::terminate()
终止运行。
- C++ 中的异常机制主要通过
异常注意事项
- C++ 异常是按类型匹配,可以按异常对象的类型分发到不同的
catch
语句块。 - 异常发生时,局部对象会自动析构(保证资源释放),这叫栈展开(Stack Unwinding)。
- 可以自定义异常类,通常继承自
std::exception
。 - 不建议抛出裸指针或基本类型,应抛出类对象,利于扩展。
- C++ 异常是按类型匹配,可以按异常对象的类型分发到不同的
C++ 中为什么在性能要求高的地方不能滥用异常
在性能敏感的场景下,不建议滥用 C++ 异常机制。因为即使不抛出异常,异常机制本身也增加了代码量和隐藏开销;而一旦抛出异常,栈展开、资源清理的代价非常高,会严重影响程序性能。
(1) 异常会增加隐藏的运行开销
- 即使程序正常执行、没有抛异常,编译器也要为了异常处理生成额外的隐藏代码(比如保存上下文信息、支持栈展开)。
- 这导致了指令更多、代码膨胀、缓存命中率降低,从而影响执行性能。
(2) 抛出异常本身是非常昂贵的操作
- 抛出异常(
throw
)时,会:- 搜索调用栈上所有活跃的
catch
语句块。 - 执行栈展开(调用析构函数释放资源)。
- 搜索调用栈上所有活跃的
- 这些操作比普通的返回、跳转要慢几个数量级;在一些测试中,
throw
一次比普通函数调用慢几十倍甚至上百倍。
- 抛出异常(
(3) 异常破坏了分支预测和局部性
- 高性能系统(比如游戏引擎、交易系统)特别依赖 CPU 的分支预测和缓存局部性。
- 异常的流程是非线性跳转,让 CPU 很难预测,大大降低执行效率。
提示
- 正常流程不应该依赖异常(异常是处理真正意外情况的,不是常规控制逻辑)。
- 如果追求极致性能,常用错误码返回(比如
return false;
或status code
),而不是抛出异常。 - C++ 11 及以后(现代 C++)提倡在可以预测的错误路径上使用
noexcept
保证无异常,提高优化空间。
C++ 中的早绑定和晚绑定
在 C++ 中,早绑定是编译期确定函数调用,比如普通成员函数、重载函数;而晚绑定是运行期根据对象的实际类型确定调用,依赖虚函数和虚函数表。一般非虚函数使用早绑定,虚函数使用晚绑定。
早绑定
- 又叫 “静态绑定”
- 在编译时就确定了调用哪个函数。
- 早绑定适用于:
- 普通成员函数(非虚函数)
- 重载函数(函数名相同,参数不同)
- 静态成员函数
- 早绑定的特点是效率高,因为编译器可以直接生成调用代码,没有运行时开销。
晚绑定
- 又叫 “动态绑定”
- 在运行时根据对象的实际类型确定调用哪个函数。
- 晚绑定适用于:
- 虚函数(
virtual
修饰的函数)
- 虚函数(
- 晚绑定依靠虚函数表(
vtable
)和虚函数表指针(vptr
)来实现。
什么是绑定
绑定指的是:在程序中,把函数调用与实际的函数实现对应起来的过程。
C++ 中指针和引用的区别(反汇编的角度)
在语法层面,指针和引用有较大区别,但从底层反汇编来看,引用通常也是通过隐藏的指针实现的,二者机器指令上差异非常小;不同点在于编译器对引用自动处理了解引用操作,使得引用使用起来更接近对象本身,更安全直观。
指针和引用的深入对比
- 语义上的区别
指针(Pointer) | 引用(Reference) | |
---|---|---|
绑定关系 | 可以随时指向不同对象 | 初始化后必须绑定一个对象,不能再改 |
是否可以为空 | 可以是 nullptr | 理论上不能为空(但野引用有风险) |
语法操作 | *p 解引用,p-> 访问成员 | 直接用引用名访问,像别名一样 |
内存开销 | 占一个指针大小 | 底层的实现通常也是一个指针 |
多级概念 | 有多级指针 | 没有多级引用 |
- 反汇编上的区别
- 反汇编层面,指针和引用几乎没有本质差别。
- 主要区别是:
- 指针:需要程序员显式操作(
*
、->
),比如需要解引用(*
)才能访问实际对象。 - 引用:在大部分编译器实现里,底层也是一个隐藏的指针,但是编译器帮程序员自动完成了解引用动作,所以语法上看起来像直接操作对象。
- 指针:需要程序员显式操作(
提示
- 在优化后的汇编代码中,如果编译器确定引用不会为
nullptr
,可以进一步做优化(比如省略空指针检查),而指针一般需要显式检查。 - 有些平台(比如嵌入式)可能为了节省开销,对指针和引用处理得更细致些,但在常规 PC 编译器(如 GCC、Clang、MSVC)中,两者的性能开销接近。
C++ 中如何解决智能指针循环引用的问题
- 循环引用问题发生在两个对象相互持有对方的
shared_ptr
智能指针时,导致对象无法被销毁。 - 解决方法是:在定义对象的时候,使用强引用智能指针(
shared_ptr
);而在引用对象的时候,使用弱引用智能指针(weak_ptr
)。 weak_ptr
是一种不增加引用计数的智能指针,不会干扰shared_ptr
的生命周期控制,从而避免了循环引用问题。- 使用
weak_ptr
后,可以在需要时通过lock()
将其转换为shared_ptr
,然后再安全地访问对象。
C++ 中重载函数和虚函数底层实现的区别
重载函数在编译时通过函数签名(函数名称、参数的数量和类型)来选择具体函数版本,不需要额外的运行时开销。而虚函数依赖于虚函数表(vtable
)和虚函数表指针(vptr
)来实现动态绑定,增加了运行时的开销,特别是在多态场景中需要查找虚函数表来决定调用哪个版本的函数。
重载函数和虚函数底层实现的区别
函数解析方式:
- 重载函数:在编译时,编译器根据函数调用的参数类型来选择具体的函数。这个过程完全是静态的,基于编译时的类型信息。
- 虚函数:在运行时,根据对象的动态类型来决定调用哪个函数。这个过程是动态的,依赖于虚函数表(
vtable
)和虚函数表指针(vptr
)。
符号表管理:
- 重载函数:编译器通过名称改编(Name Mangling)来区分不同的重载函数。每个重载版本都有一个唯一的符号名,且所有的解析发生在编译期。
- 虚函数:编译器为每个包含虚函数的类生成虚函数表(
vtable
),对象实例包含一个指向该表的虚函数表指针(vptr
)。函数解析是在运行时进行的,通过查找vtable
来决定调用的虚函数。
性能开销:
- 重载函数:没有运行时开销,函数调用在编译时期确定。
- 虚函数:由于依赖虚函数表(
vtable
)和虚函数表指针(vptr
),每次调用虚函数时都会有一定的运行时开销。具体来说,每次虚函数调用需要通过对象的vptr
查找vtable
,然后找到正确的函数指针,再进行调用。
内存结构:
- 重载函数:每个函数都有一个固定的符号,编译器通过符号表来处理不同版本的重载函数。
- 虚函数:每个包含虚函数的类有一个虚函数表(
vtable
),每个对象有一个指向该表的虚函数表指针(vptr
),通过该指针实现运行时多态。
重载函数和虚函数底层实现的总结
重载函数 | 虚函数 | |
---|---|---|
解析方式 | 编译时静态解析 | 运行时动态解析 |
符号管理 | 名称改编(Name Mangling) | 虚函数表(vtable ) |
性能开销 | 无运行时开销 | 每次调用虚函数时有额外开销(查找 vtable ) |
内存结构 | 不需要 vtable ,直接通过符号表 | 需要 vtable 和 vptr ,增加内存开销 |
C++ 中 map 的底层实现,AVL 和 RBTree 有什么区别
map
的底层实现map
是一种有序关联容器,底层是基于红黑树(RBTree)实现的。- 红黑树是一种自平衡的二叉查找树,它保证了树的高度在
O(log n)
的范围内,从而保证了查找、插入和删除操作的时间复杂度为O(log n)
。 - 在
map
中,键(Key)是唯一的,而且数据会根据键值的大小进行排序。每个节点包含一个键值对,查找、插入和删除操作都利用红黑树的性质进行。
AVL 树和 RBTree 树的区别
- AVL 树和红黑树都是自平衡二叉查找树,主要用于保证查找操作的时间复杂度为
O (log n)
。 - AVL 树的平衡更严格,查询性能更好,但在插入和删除时需要更多的旋转。
- 红黑树的平衡度较为宽松,插入和删除的旋转操作较少,但查询性能可能稍差于 AVL 树。
- AVL 树和红黑树都是自平衡二叉查找树,主要用于保证查找操作的时间复杂度为
AVL 树和 RBTree 树的深入对比
平衡条件
- AVL 树:
- AVL 树是一种严格平衡的二叉查找树。它要求树中每个节点的左右子树的高度差不超过 1,即 平衡因子(左右子树的高度差)只能是
-1
、0
或1
。 - 由于这种严格平衡的要求,AVL 树的插入和删除操作可能需要较多的旋转来恢复平衡。
- AVL 树是一种严格平衡的二叉查找树。它要求树中每个节点的左右子树的高度差不超过 1,即 平衡因子(左右子树的高度差)只能是
- 红黑树:
- 红黑树对平衡的要求较为宽松。它通过给每个节点着色(红色或黑色)来实现平衡,满足以下规则:
- 每个节点是红色或黑色。
- 根节点是黑色。
- 红色节点不能有红色的子节点(即没有两个连续的红色节点)。
- 从任一节点到其所有叶节点的路径上,黑色节点的个数相同。
- 红黑树的平衡度不如 AVL 树严格,但它的插入和删除操作通常较为高效,尤其是在频繁修改数据的情况下。
- 红黑树对平衡的要求较为宽松。它通过给每个节点着色(红色或黑色)来实现平衡,满足以下规则:
- AVL 树:
插入和删除
- AVL 树:
- 插入和删除操作较为复杂,因为每次操作后都要保持严格的平衡条件。通常需要通过 “单旋转” 或 “双旋转” 来调整树结构。
- 由于要求严格平衡,AVL 树在插入和删除操作中可能会有较多的旋转。
- 红黑树:
- 红黑树的插入和删除操作相对简单一些。插入时,通过颜色的改变和较少的旋转来恢复平衡。删除时,也有特定的调整策略来保持树的平衡。
- AVL 树:
查询效率
- AVL 树:
- 由于 AVL 树的平衡条件更严格,通常能保证更小的树高度,查找操作相对更快。查询操作的时间复杂度为
O(log n)
,但由于需要更多的旋转,性能在插入和删除时可能受到影响。
- 由于 AVL 树的平衡条件更严格,通常能保证更小的树高度,查找操作相对更快。查询操作的时间复杂度为
- 红黑树:
- 红黑树的平衡度相对较宽松,因此查找操作的效率略低于 AVL 树,但由于插入和删除操作需要的旋转较少,整体性能在一些情况下更为稳定。
- AVL 树:
性能对比
- AVL 树:
- 在查找操作频繁的情况下,AVL 树通常会表现得更好,因为它的高度比红黑树更小。
- 但是在插入和删除操作频繁的场景中,AVL 树的旋转操作会导致性能下降。
- 红黑树:
- 在插入和删除操作频繁的场景中,红黑树通常表现得更好,因为它需要较少的旋转,调整较为简单。
- 但是在查询操作上,红黑树的性能可能会稍微逊色于 AVL 树。
- AVL 树:
C++ 中假如 map 的键是类类型,那么 map 底层是如何比较的
- 在 C++ 中,当
map
的键(Key)是类类型时,底层红黑树需要对键进行大小比较。默认是使用键类型的<
运算符重载函数。 - 如果类类型没有提供
<
运算符重载函数,则可以自定义一个比较器,并在map
模板参数中指定。比较器需要符合严格弱序性,以保证红黑树能正常维护平衡和有序性。
C++ 中设计模式知道哪些,具体讲一下
- C++ 中设计模式分为创建型、结构型、行为型三大类,我比较熟悉的包括单例模式、工厂模式、观察者模式、策略模式、代理模式、适配器模式、装饰器模式等。
- 以单例模式为例,它通过私有化构造函数和提供全局访问点,保证系统中只有一个实例,常用于日志记录、配置管理等场景。
C++ 中 stack 和 queue 的底层实现
stack
和queue
都是容器适配器,默认是基于deque
容器实现。stack
主要通过push_back()
、pop_back()
来实现后进先出(LIFO),queue
主要通过push_back()
、pop_front()
来实现先进先出(FIFO)。- 底层之所以选用
deque
,是因为deque
在头部和尾部都能提供常数时间(O(1)
)的插入和删除操作,性能符合stack
和queue
的特点。
提示
- 在 C++ 中,
stack
和queue
都允许自定义底层容器,比如可以用vector
来作为底层容器(只要它支持需要的接口),即stack<int, std::vector<int>> s;
。
C++ 中如果构造函数里面抛出了异常,会发生什么,如何解决
- 如果 C++ 构造函数抛出异常,当前对象不会构造成功,编译器会自动析构已经成功构造的成员对象和基类对象,防止资源泄漏。
- 为了避免内存泄漏,通常遵循 RAII 原则,比如使用智能指针等自动资源管理工具来避免手动释放资源。
- 在 C++ 11 之后,可以使用
noexcept
明确声明构造函数不会抛出异常,这对性能优化、标准容器(如vector
)的内部移动操作有重要意义。