=default用于显式要求编译器生成特殊成员函数的默认实现,=delete用于彻底禁用函数;二者仅适用于构造、析构、拷贝/移动函数,需在类内声明且不可有函数体,组合使用可精准表达资源管理意图。

default 关键字:显式要求编译器生成默认实现
当类中定义了任意构造函数(比如带参构造),编译器就不会自动生成默认构造函数 MyClass();但有时你仍需要它,又不想手写空实现——这时用 = default 告诉编译器“请按老规矩生成”。它只能用于特殊成员函数:构造函数、析构函数、拷贝/移动构造函数、拷贝/移动赋值运算符。
注意:必须放在类内声明处,且不能有函数体;放在类外定义时,需同时指定 inline(否则链接错误):
class Widget {
public:
Widget() = default; // ✅ 类内,最常用
Widget(const Widget&) = default;
~Widget() = default;
Widget& operator=(const Widget&) = default;
};
常见误用:
- 给已定义实现的函数加
= default→ 编译错误 - 在类外写
Widget::Widget() = default;却没加inline→ LNK2005 或 undefined reference - 对普通成员函数使用
= default→ 不合法,仅限特殊成员函数
delete 关键字:彻底禁用某个函数
= delete 是比私有化更彻底的禁用方式。它让函数在编译期就不可调用,连友元和类内代码都不能用。典型用途是禁止拷贝、禁用不安全的类型转换、或封禁特定参数组合的重载。
立即学习“C++免费学习笔记(深入)”;
例如禁用拷贝:
class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
其他实用场景:
- 禁用
int到类的隐式构造:explicit MyClass(int) = default;不行,得写MyClass(int) = delete;配合其他构造函数 - 禁用指针参数的重载(防止传
nullptr):void process(char*) = delete; - 禁用模板实例化:
template,再特化允许的类型void foo(T) = delete;
关键点:被 = delete 的函数仍参与重载决议,只是匹配后立即报错 —— 所以它能精准拦截“不该发生的调用”,比运行期断言更早发现问题。
default 和 delete 的组合使用场景
现代 C++ 中二者常配合,表达清晰的资源管理意图。比如实现移动语义但禁用拷贝:
class UniquePtr {
public:
UniquePtr() = default;
UniquePtr(UniquePtr&&) = default; // 允许移动
UniquePtr(const UniquePtr&) = delete; // 禁止拷贝
UniquePtr& operator=(UniquePtr&&) = default;
UniquePtr& operator=(const UniquePtr&) = delete;
};
另一个典型是 PIMPL 模式中,把拷贝操作显式 = delete,而将默认构造、析构设为 = default(即使它们在类外定义,也需加 inline 或在头文件中定义)。
容易忽略的细节:
-
= default的函数仍是public且noexcept(如果编译器生成的版本是);= delete的函数访问权限无关紧要,它根本不会进入调用流程 - 若基类的某个函数是
= delete,派生类中同签名函数不会自动继承该删除状态,必须显式再次= delete - 在模板中,
= delete可用于 SFINAE 替代方案,但要注意它不导致 SFINAE 失败,而是硬错误 —— 如需 SFINAE,请用enable_if
为什么不能只靠 private + 不实现来禁用?
传统做法是把拷贝构造函数声明为 private 且不定义,但存在明显缺陷:
- 类内成员和友元仍可调用,无法真正封禁
- 链接期才报错(undefined symbol),错误信息模糊,定位困难
- 无法禁用内置类型转换、模板推导等更隐蔽的调用路径
= delete 在编译期拦截所有调用,错误位置精确到行号,消息明确提示 “call to deleted function”。这是类型安全的关键升级。
真正复杂的点在于:什么时候该用 = default 而不是手写?答案是——当你需要的是“编译器原本会生成的那个行为”,且该行为符合你的语义(如 trivially copyable),就用 = default;一旦涉及自定义逻辑(比如日志、验证、资源预分配),就必须手写。










