在 C++ 编程中,我们有时需要设计禁止拷贝的类 。
比如单例类、管理唯一资源(如文件句柄、网络连接)的类。
拷贝这类对象会导致资源重复释放、逻辑错误等问题。
本文将详细讲解 C++98 和 C++11 两种标准下,实现 "禁止类拷贝" 的方法、原理及最佳实践。
一、为什么要禁止拷贝?
拷贝操作在 C++ 中主要发生在两个场景:
- 拷贝构造函数 :用已有对象创建新对象时调用(如 CopyBan obj2(obj1)
;); - 赋值运算符重载 :用一个对象给另一个已存在的对象赋值时调用(如 obj2 = obj1
;)。
想要禁止类的拷贝,核心就是让这两个函数无法被调用------ 无论是类外部代码,还是类内部成员函数,都无法触发拷贝操作。
二、C++98 实现方案:私有化 + 只声明不定义
C++98 没有专门的 "禁用函数" 语法,只能通过 "访问权限 + 函数声明策略" 实现禁止拷贝。
1. 代码实现
cpp
class CopyBan
{
public:
// 普通构造函数(示例)
CopyBan(int data = 0) : _data(data) {}
// 其他成员函数(示例)
void Print() const
{
std::cout << "Data: " << _data << std::endl;
}
private:
// 关键:只声明、不定义,且设为私有
CopyBan(const CopyBan&); // 拷贝构造函数
CopyBan& operator=(const CopyBan&); // 赋值运算符重载
// 类成员变量(示例)
int _data;
};
2. 核心原理
(1)设为私有:防止外部定义 / 调用
如果仅声明但不设置为 private,用户可能在类外自行定义这两个函数,导致 "禁止拷贝" 的目的失效。
- 外部代码调用拷贝构造 / 赋值运算符时,会因 "访问私有成员" 直接编译报错;
- 即使是类的友元函数,也无法调用(因为函数只有声明、没有定义,链接阶段会报错)。
(2)只声明不定义:防止内部拷贝
不定义这两个函数的原因:
- 这两个函数本就不应该被调用,定义无任何实际意义;
- 如果定义了,类的成员函数 / 友元函数可能在内部触发拷贝(比如
void func() { CopyBan tmp(*this); }),违背 "禁止拷贝" 的初衷; - 仅声明不定义时,若内部误调用,链接阶段会报 "未定义的引用" 错误,及时暴露问题。
3. 测试验证
cpp
int main()
{
CopyBan obj1(10);
CopyBan obj2(obj1); // 编译报错:拷贝构造函数是私有成员
CopyBan obj3;
obj3 = obj1; // 编译报错:赋值运算符是私有成员
return 0;
}
三、C++11 实现方案:=delete 显式删除
C++11 扩展了**delete** 的用法:在默认成员函数后加 =delete ,表示让编译器删除该函数,直接禁止其调用(比 C++98 更简洁、语义更清晰)。
1. 代码实现
cpp
class CopyBan
{
public:
// 普通构造函数(示例)
CopyBan(int data = 0) : _data(data) {}
// 关键:显式删除拷贝构造和赋值运算符
CopyBan(const CopyBan&) = delete; // 删除拷贝构造函数
CopyBan& operator=(const CopyBan&) = delete; // 删除赋值运算符重载
// 其他成员函数(示例)
void Print() const
{
std::cout << "Data: " << _data << std::endl;
}
private:
// 类成员变量(示例)
int _data;
};
2. 核心原理
=delete是编译期限制:编译器会直接拒绝生成 / 调用这两个函数,无论类内、类外调用,都会在编译阶段报错(比 C++98 的 "链接期报错" 更及时);- 无需设置访问权限(即使写在
public里,也无法调用),语义更直观 ------"明确告诉编译器:我要删除这个函数"。
3. 测试验证
cpp
int main()
{
CopyBan obj1(10);
CopyBan obj2(obj1); // 编译报错:使用了被删除的函数
CopyBan obj3;
obj3 = obj1; // 编译报错:使用了被删除的函数
return 0;
}
四、两种方案对比
| 特性 | C++98 方案 | C++11 方案 |
|---|---|---|
| 实现方式 | 私有化 + 只声明不定义 | 显式 =delete 删除函数 |
| 报错阶段 | 编译(外部调用)/ 链接(内部调用) | 编译期直接报错 |
| 语义清晰度 | 间接实现,需理解 "私有 + 未定义" 逻辑 | 直接、直观,语义明确 |
| 适用场景 | 旧编译器、C++98 环境 | 现代 C++ 开发(推荐) |