new/delete 手动管理 → 中途 return/异常会泄漏=delete),编译器默认生成拷贝构造(声明了析构则移动构造不生成)→ 能编译;显式 =delete 才不能=delete / 未定义 / 非 noexcept)才退回拷贝 → 这就是"只能移动不能拷贝"的对象(unique_ptr)也能存 vector 的原因一句话:"构造=创建时自动初始化,析构=销毁时自动清理;RAII 是栈对象自动析构,不是 new/delete;移动给临时对象、拷贝给存活对象;有指针成员必须深拷贝;容器默认走移动,移动不可用才走拷贝。"
构造函数 = 对象创建时自动执行、负责初始化/获取资源;析构函数 = 对象销毁时自动执行、负责清理/释放资源。构造可重载多个(默认/带参/拷贝/移动),析构只能一个。
| 构造函数 | 析构函数 | |
|---|---|---|
| 调用时机 | 对象创建时 | 对象销毁时 |
| 个数 | 可重载多个 | 只能一个 |
| 命名 | 与类名相同 | 类名前加 ~ |
| 能否重载 | ✅ | ❌ |
| 用途 | 初始化成员、获取资源 | 清理资源、释放内存 |
| 默认构造 | 拷贝构造 | 移动构造 |
|---|---|---|
ClassName() |
ClassName(const ClassName&) |
ClassName(ClassName&&) |
| 无参,编译器默认生成 | 用已存在对象建新对象(深拷贝) | 移动临时对象资源(性能优化) |
| 数据初始化 | 数据复制 | 资源转移,原对象置空 |
FileGuard guard(fp);),不用 new/delete错误写法:new/delete 手动管理,中间 return → delete 不执行 → 泄漏
class FileGuard {
public:
FileGuard(FILE* f) : fp(f) {}
~FileGuard() { fclose(fp); }
private:
FILE* fp;
};
void func() {
FILE* fp = fopen("a.txt", "w");
FileGuard guard(fp); // 栈对象,作用域结束自动 fclose
}
| 浅拷贝 | 深拷贝 | 移动 | |
|---|---|---|---|
| 指针成员 | 复制指针值(共享内存) | 新分配内存并复制数据 | 偷走指针,原对象置空 |
| 两个对象 | 指向同一块内存 | 完全独立 | 一个持有资源,一个空 |
| 后果 | 双重释放崩溃 | 安全 | 安全且 O(1) |
| 何时用 | 禁用 | 存活的已存在对象 | 临时对象 |
ClassName(ClassName&&)other.m_data = nullptr;——防止原对象析构时 delete 同一块内存 → 双重释放崩溃移动口诀:拷贝 = 复制一份,移动 = 把东西搬走并给原对象留个空盒子。
class Buffer {
public:
Buffer(size_t size) : m_size(size) {
m_data = new char[m_size];
}
// 拷贝构造:深拷贝
Buffer(const Buffer& other) : m_size(other.m_size) {
m_data = new char[m_size];
memcpy(m_data, other.m_data, m_size);
}
// 移动构造:资源转移,原对象置空防双重释放
Buffer(Buffer&& other) noexcept : m_data(other.m_data), m_size(other.m_size) {
other.m_data = nullptr;
other.m_size = 0;
}
~Buffer() { delete[] m_data; } // nullptr 时 delete 安全
private:
char* m_data;
size_t m_size;
};
标准库容器(vector/string 等)在重定位元素(扩容搬移)时:
=delete、未定义、或非 noexcept 时,退回拷贝构造(O(n) 复制)noexcept 很关键:vector 扩容必须保证强异常安全(失败则回滚原状)。如果移动可能抛异常,vector 无法回滚(元素已被搬走),所以会退回用拷贝;移动构造声明 noexcept 后,vector 才放心用移动std::unique_ptr(只移动、不能拷贝)能安全存进 vector——扩容时用移动构造,原指针置空return T();、T t = T(); 等场景,C++17 保证直接在目标位置构造,不调用任何拷贝/移动构造=delete、移动构造也不存在,这种代码在 C++17 下依然能编译return T() 仍需要移动或拷贝构造(会走移动,移动不可用才拷贝)——嵌入式面试按 C++11 讲,C++17 是加分项| 自动管理(RAII) | 手动管理(new/delete) | |
|---|---|---|
| 资源释放 | 析构自动执行,绝不遗漏 | 必须手动 delete,return/异常就漏 |
| 安全 | ✅ | ⚠️ 易泄漏 |
| 典型 | 栈对象、unique_ptr、vector | 裸指针、new[] |
| 拷贝 | 移动 |
|---|---|
| 给"还要继续用的已存在对象" | 给"用完即弃的临时对象" |
| 深拷贝,数据独立 | 偷资源,原对象置空 |
| O(n) 复制数据 | O(1) 搬指针 |
| 移动不可用时的兜底 | 容器默认优先使用 |
| C++11 | C++17 |
|---|---|
return T() 需要移动或拷贝构造 |
保证复制省略,无需任何构造 |
| 移动可用走移动,不可用走拷贝 | 直接构造,零拷贝 |
class FileGuard { ... ~FileGuard() { fclose(fp); } }; 栈对象在 func 中间 return → 文件会正常关闭(栈对象自动析构,无论什么路径退出)std::string s2 = s1; → 拷贝(s1 是左值要复用);std::string s3 = std::string("world"); → 移动(临时右值用完即弃)class Y { Y(const Y&) = delete; };(声明了析构)→ Y a = makeY(); 在 C++11/14 下 编译失败(拷贝被 delete,移动因声明析构不生成);C++17 下能编译(保证复制省略,直接构造不调用拷贝/移动)noexcept,vector 才敢用它(保证强异常安全);unique_ptr 只移动不拷贝也能存 vectorreturn T() 直接构造,拷贝被 =delete 也能编译(加分项)| 相邻概念 | 关系 | 孤立理解会犯的错 |
|---|---|---|
| RAII vs 资源管理 | RAII 是 C++ 防泄漏的根基,比手动 new/delete 安全 | 以为 new/delete 就是 RAII |
| struct vs class(主题44) | 两者都有构造/析构,用法相同 | 以为 struct 没有构造析构 |
| 堆 vs 栈(主题11) | 栈对象自动析构(RAII),堆对象需手动 delete | 以为 new 出来的对象也会自动清理 |
| 内存泄漏(主题12) | 手动 new 无对应 delete = 泄漏;RAII 根治 | 靠手动记得 delete,不靠机制 |
| 原子/锁(主题21/27) | RAII 常用于锁管理(构造加锁、析构解锁) | 手动 lock/unlock,中途 return 死锁 |
2026-08-26
每主题一页,复习时只翻本目录。