构造与析构开销小,性能瓶颈在于其中的重操作;应避免构造中深拷贝/堆分配,用委托构造减少重复,析构中只做快速无异常操作,移动构造须显式定义并标noexcept。

构造与析构本身开销极小,真正拖慢性能的是你在里面干的事——比如反复 new/delete、拷贝大块内存、调用低效初始化逻辑。
避免在构造函数里做深拷贝或堆分配
常见错误是把资源申请和数据复制全塞进构造函数体,比如:
class Buffer {
char* data_;
public:
Buffer(size_t n) {
data_ = new char[n]; // ✅ 分配没问题
std::memset(data_, 0, n); // ⚠️ 但 memset 大块内存很慢
// 如果还在这里 memcpy 一个 vector 的内容,就更糟
}
};实际场景中,这类初始化常被重复触发(如 std::vector 扩容、函数按值返回),导致大量冗余操作。
- 把可延迟的初始化(如清零、填充)移到首次使用时做,或用 lazy init 模式
- 若必须预分配,优先用
std::vector替代裸指针,它内部对小容量有优化,且移动成本低 - 构造函数体里只做轻量级赋值;重活交给
init()或 move-from 场景下的移动构造
用委托构造减少重复逻辑
多个构造函数共享初始化代码时,别靠复制粘贴,否则改一处漏三处。比如下面这种写法极易出错:
立即学习“C++免费学习笔记(深入)”;
class Config {
int timeout_;
bool debug_;
public:
Config() : timeout_(30), debug_(false) {}
Config(int t) : timeout_(t), debug_(false) {}
Config(int t, bool d) : timeout_(t), debug_(d) {}
};正确做法是让非默认构造函数委托给最完备的那个:
Config(int t, bool d) : timeout_(t), debug_(d) {}
Config(int t) : Config(t, false) {} // ✅ 委托
Config() : Config(30, false) {} // ✅ 委托- 委托必须出现在初始化列表,不能在函数体内写
this->Config(...) - 被委托的构造函数会完整执行,包括其初始化列表和函数体
- 编译器通常能内联委托调用,生成的汇编和直写几乎一样
析构函数里别做耗时操作,尤其禁止抛异常
析构函数被调用的时机不可控(比如栈展开时),一旦它抛异常,程序直接终止。更隐蔽的问题是:
- 在析构里做日志、网络请求、磁盘写入 —— 这些都可能阻塞或失败
- 调用虚函数(尤其是基类析构中调派生类虚函数),行为未定义
- 释放资源顺序错误,比如先关文件句柄再删临时路径,路径已不存在
标准做法是:
- 析构只做确定性、快速、无异常的操作:
delete、close()、reset() - 把清理逻辑拆成显式
shutdown()或flush()成员函数,由用户控制调用时机 - 如果类有虚函数,基类析构必须声明为
virtual,否则派生类析构不执行
移动构造必须写,并标 noexcept
没写移动构造的类,在容器扩容、值返回等场景下会退化为拷贝构造,性能断崖式下跌。例如:
std::vector<HeavyObject> v;
v.reserve(10000);
for (int i = 0; i < 10000; ++i) {
v.push_back(HeavyObject(i)); // 若没移动构造,每次扩容都深拷贝所有旧元素
}手写移动构造的关键点:
- 参数是
HeavyObject&&,内部把源对象的指针/句柄直接拿过来 - 必须把源对象的资源指针置空(如
other.ptr_ = nullptr),否则析构时双重释放 - 务必加
noexcept,否则std::vector不敢用移动,宁可拷贝 - 如果类管理了多个资源(如内存 + 文件句柄),移动后要确保每个都被置空或移交
最容易被忽略的一点:移动后源对象仍需满足“可析构”前提 —— 即使它已经是个空壳,析构函数也不能崩。


















