你想弄明白C++中= delete和= default这两个语法的区别与用法,它们是C++11引入的核心特性,主要用于精准控制类的特殊成员函数(如构造函数、拷贝构造、赋值运算符等)。我会用通俗的语言+实战示例,讲清两者的核心用途、区别和使用场景,结合你之前接触的单例模式、类模板等场景加深理解。
一、核心定义(先立概念)
| 语法 | 核心作用 | 通俗理解 |
|---|---|---|
= default | 显式要求编译器生成默认版本的特殊成员函数(如默认构造、拷贝构造) | “编译器,按你的默认规则帮我生成这个函数” |
= delete | 显式禁用某个函数(让编译器拒绝生成/禁用调用) | “编译器,这个函数我不用,禁止任何人调用” |
二、= default:显式生成默认函数
1. 为什么需要= default?
C++编译器会为类隐式生成默认构造、拷贝构造、赋值运算符、析构函数等特殊成员函数,但如果类中自定义了某些函数(如带参数的构造函数),编译器就会停止生成默认版本。= default可以强制编译器重新生成这些默认函数,既保留自定义逻辑,又不丢失默认行为。
2. 实战示例
#include<iostream>usingnamespacestd;classPerson{private:string name;intage;public:// 自定义带参数的构造函数(编译器不再生成默认构造)Person(string n,inta):name(n),age(a){}// 显式要求编译器生成默认构造函数(= default)Person()=default;// 显式要求编译器生成默认拷贝构造函数Person(constPerson&)=default;// 显式要求编译器生成默认赋值运算符Person&operator=(constPerson&)=default;// 显式要求编译器生成默认析构函数~Person()=default;voidshow(){cout<<"name: "<<name<<", age: "<<age<<endl;}};intmain(){// 调用默认构造函数(因= default生效)Person p1;p1.show();// 输出:name: , age: 0// 调用自定义构造函数Personp2("Tom",18);p2.show();// 输出:name: Tom, age: 18// 调用默认拷贝构造函数Person p3=p2;p3.show();// 输出:name: Tom, age: 18// 调用默认赋值运算符p1=p2;p1.show();// 输出:name: Tom, age: 18return0;}3. 关键细节
= default只能用于编译器可自动生成的特殊成员函数(默认构造、拷贝构造、移动构造、赋值运算符、析构函数等),不能用于普通函数;- 用
= default生成的函数比手动写的“空函数”更高效(编译器会优化为原生代码); - 示例中如果不加
Person() = default;,Person p1;会编译报错(编译器未生成默认构造)。
三、= delete:显式禁用函数
1. 为什么需要= delete?
用于禁止某些函数的调用或生成,最常见场景是:
- 禁用拷贝构造/赋值运算符(如单例模式,保证实例唯一);
- 禁用特定类型的函数重载(如禁止int隐式转换为double);
- 禁用编译器自动生成的特殊成员函数。
2. 实战场景1:单例模式(禁用拷贝/赋值)
这是你之前接触的场景,用= delete保证类实例唯一:
#include<iostream>usingnamespacestd;classSingleton{private:staticSingleton instance;// 私有构造函数(禁止外部创建)Singleton(){cout<<"Singleton创建"<<endl;}// 禁用拷贝构造(= delete)Singleton(constSingleton&)=delete;// 禁用赋值运算符(= delete)Singleton&operator=(constSingleton&)=delete;public:staticSingleton&getInstance(){returninstance;}};Singleton Singleton::instance;intmain(){Singleton&s1=Singleton::getInstance();// 以下代码编译报错(因拷贝/赋值被禁用)// Singleton s2 = s1;// Singleton s3;// s3 = s1;return0;}3. 实战场景2:禁用特定函数重载
#include<iostream>usingnamespacestd;// 函数模板:计算两个数的和template<typenameT>Tadd(T a,T b){returna+b;}// 禁用int版本的add(= delete)template<>intadd<int>(inta,intb)=delete;intmain(){// 正常调用double版本cout<<add(3.14,5.67)<<endl;// 输出:8.81// 编译报错(int版本被delete)// cout << add(1, 2) << endl;return0;}4. 关键细节
= delete可用于任意函数(特殊成员函数、普通函数、模板函数),比= default适用范围广;- 被
= delete的函数即使声明了,也无法调用(编译期直接报错); - C++03中禁用拷贝构造的方式是“声明为private且不实现”,C++11后推荐用
= delete(更清晰、编译期检测)。
四、= defaultvs= delete核心区别
| 维度 | = default | = delete |
|---|---|---|
| 核心目的 | 让编译器生成默认版本的函数 | 让编译器禁用某个函数(拒绝生成/调用) |
| 适用范围 | 仅特殊成员函数(编译器可自动生成的) | 任意函数(特殊成员、普通函数、模板函数) |
| 编译行为 | 生成函数代码,允许调用 | 拒绝生成函数代码,调用时报错 |
| 典型场景 | 自定义构造后恢复默认构造、优化默认函数 | 单例禁用拷贝、禁止特定类型的函数调用 |
五、常见坑点与最佳实践
1. 坑点1:误用= default/= delete
// 错误:普通函数不能用= defaultvoidfunc()=default;// 错误:析构函数delete后,类无法实例化(对象销毁时需要析构)~Person()=delete;Person p;// 编译报错2. 坑点2:默认函数的生成规则
- 如果类中自定义了析构函数,编译器不再自动生成移动构造/移动赋值;
- 如果用
= delete禁用了拷贝构造,编译器也不会自动生成移动构造; - 建议:要么显式用
= default指定,要么显式用= delete禁用,避免依赖编译器的隐式规则。
3. 最佳实践
- 单例模式:必用
= delete禁用拷贝构造和赋值运算符; - 需要默认构造但又自定义了带参构造:用
= default恢复默认构造; - 禁止隐式类型转换:用
= delete禁用特定重载(如add(int)); - 优化性能:对编译器自动生成的函数用
= default(比手动写空函数高效)。
总结
= default:显式要求编译器生成默认版本的特殊成员函数,解决“自定义构造后丢失默认函数”的问题,且更高效;= delete:显式禁用函数,核心用于单例模式禁用拷贝、禁止特定函数调用,是C++11后替代“私有不实现”的最佳方式;- 两者都是C++11的语法糖,核心价值是精准控制类的函数生成规则,让代码更清晰、更符合预期;
- 记忆口诀:
default是“要默认”,delete是“要禁用”。
这两个语法在现代C++(尤其是嵌入式、高性能开发)中非常常用,掌握它们能让你写出更规范、更高效的类设计代码。