= delete 是 C++11 起禁用拷贝构造和拷贝赋值的最直接安全方式,编译期硬拦截、错误明确,优于私有化未定义;必须在类内声明,需成对删除,不自动禁用移动操作,且需配合 explicit 防隐式转换绕过。

在 C++11 及以后,delete 是禁用拷贝构造函数和拷贝赋值运算符最直接、最安全的方式——它比私有化 + 不定义更清晰,编译期报错更明确,且能阻止隐式转换参与重载决议。
为什么用 = delete 而不是私有化
旧式写法(C++03 风格)常把拷贝构造函数声明为 private 且不定义,指望链接时报错。但问题不少:
- 编译器可能在模板实例化或友元访问时“绕过”私有检查,导致意外通过
- 错误信息模糊,常是链接错误(
undefined reference to 'A::A(A const&)'),而非直观的“被删除”提示 - 无法阻止隐式转换后参与拷贝初始化(比如
A a = 42;若存在A(int)构造函数,仍可能触发拷贝)
= delete 是编译期硬拦截:只要代码路径中涉及该函数调用,立即报错,且错误位置精准。
= delete 必须显式写在类定义内
禁用拷贝必须在类内部声明,不能在类外 delete 成员函数。常见错误写法:
class NonCopyable {
public:
NonCopyable() = default;
};
NonCopyable::NonCopyable(const NonCopyable&) = delete; // ❌ 错误:不能在类外定义/删除默认成员函数
正确写法:
class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete; // ✅ 拷贝构造
NonCopyable& operator=(const NonCopyable&) = delete; // ✅ 拷贝赋值
};
注意:如果只删拷贝构造但没删拷贝赋值,对象仍可被赋值;反之亦然。二者通常需成对删除。
禁用拷贝后,移动操作不会自动禁用
= delete 对移动操作无传染性。若类含资源(如裸指针、文件句柄),禁用拷贝后,你很可能需要显式定义移动构造/赋值,否则:
- 编译器不会自动生成移动函数(因为存在用户声明的拷贝函数)
- 对象将既不可拷贝,也不可移动,变成“纯不可复制”——这有时正是你想要的,但更多时候是疏忽
例如:
class Buffer {
char* data_;
public:
Buffer(size_t n) : data_(new char[n]) {}
Buffer(const Buffer&) = delete;
Buffer& operator=(const Buffer&) = delete;
// ❌ 缺少 move 构造/赋值 → Buffer 对象无法用于 std::vector::push_back 等场景
};
如需支持移动,请显式添加:
Buffer(Buffer&& other) noexcept : data_(other.data_) { other.data_ = nullptr; }
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) { delete[] data_; data_ = other.data_; other.data_ = nullptr; }
return *this;
}
= delete 能有效拦截隐式转换引发的拷贝
这是很多人忽略的关键点:当存在单参构造函数时,隐式转换可能“绕过”拷贝禁用逻辑。比如:
class Widget {
public:
Widget(int) {} // 允许 int → Widget 隐式转换
Widget(const Widget&) = delete;
};
Widget w = 42; // ✅ 编译通过!调用 Widget(int),不触发拷贝构造
但如果该构造函数也被 = delete 或 explicit,就能堵住这个口子:
class Widget {
public:
explicit Widget(int) {} // ❌ 阻止 Widget w = 42;
Widget(const Widget&) = delete;
};
// Widget w = 42; // error: no viable conversion
// Widget w(42); // ✅ OK —— 显式调用
所以,真正想彻底禁止“从其他类型间接构造并拷贝”,要配合 explicit 和 = delete 双重控制。
别忘了:只要类里出现了任何用户声明的构造函数(哪怕只是 = delete),编译器就不会再合成默认构造函数。如果还需要默认构造,得显式写 Widget() = default;。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











