目录
[一、C++ 默认生成的函数](#一、C++ 默认生成的函数)
[1. 将拷贝函数放到 private 中(但不定义)](#1. 将拷贝函数放到 private 中(但不定义))
[2. 友元也无法绕过](#2. 友元也无法绕过)
[1. 定义 uncopyable 基类](#1. 定义 uncopyable 基类)
[2. 派生类继承 nocopy](#2. 派生类继承 nocopy)
[3. 原理](#3. 原理)
一、C++ 默认生成的函数
如果不显式声明,C++ 编译器会自动生成以下函数(都是 public 且 inline 的):
-
默认构造函数
-
拷贝构造函数
-
拷贝赋值运算符
-
析构函数
二、何时会拒绝生成?
在某些情况下,编译器可能拒绝生成默认的拷贝函数:
-
类中有 const 成员:const 成员不能被赋值,因此拷贝赋值运算符需要自定义
-
类中有引用成员:引用必须在初始化时绑定,不能被重新赋值
-
将拷贝函数放到 private 中:手动阻止生成和调用
这些情况不违背 C++ 规则,只是需要程序员自己定义合适的拷贝行为。
三、明确拒绝拷贝的方法
1. 将拷贝函数放到 private 中(但不定义)
cpp
class hause {
public:
hause(double s, const std::string& name) : _s(s), _name(name) {}
private:
// 只声明不定义,阻止编译器自动生成
hause& operator=(const hause&);
hause(const hause&);
double _s;
std::string _name;
};
说明:
-
每套房子都是唯一的,因此不允许拷贝
-
将拷贝构造函数和拷贝赋值运算符声明为
private,阻止了外部调用 -
只声明不定义 :如果成员函数或友元试图调用,会得到链接错误
2. 友元也无法绕过
cpp
class hause {
friend hause func(hause& h); // 声明友元函数
public:
hause(double s, const std::string& name) : _s(s), _name(name) {}
std::string getname() { return _name; }
private:
hause& operator=(const hause&); // 只声明不定义
hause(const hause&);
double _s;
std::string _name;
};
hause func(hause& h) {
hause ret = h; // 调用拷贝构造函数,只有声明没有定义
std::cout << ret.getname() << std::endl;
return ret;
}
结果:
-
虽然
func是hause的友元,可以访问private成员 -
但拷贝函数只有声明,没有定义
-
因此链接时找不到函数定义,产生链接错误

典型例子 :iostream 类家族就通过这种方式阻止拷贝。比如不能拷贝 cout、cin 对象。
四、通过继承实现(更优雅的方式)
1. 定义 uncopyable 基类
cpp
class nocopy {
public:
nocopy() {} // 允许构造和析构
~nocopy() {}
private:
nocopy& operator=(const nocopy&); // 只声明不定义
nocopy(const nocopy&);
};
2. 派生类继承 nocopy
cpp
class hause : public nocopy {
public:
hause(double s, const std::string& name) : _s(s), _name(name) {}
std::string getname() { return _name; }
private:
double _s;
std::string _name;
};
3. 原理
当尝试拷贝 hause 对象时:
-
编译器会尝试生成
hause的拷贝构造函数 -
生成的拷贝构造函数会先调用基类
nocopy的拷贝构造函数 -
但基类
nocopy的拷贝构造函数是private且未定义 -
因此编译失败(如果是在成员函数内尝试拷贝,则产生链接错误)
优点:
-
更清晰地表达意图:
hause类"不可拷贝" -
不需要在每个要禁止拷贝的类中重复声明 private 拷贝函数
-
错误信息通常在编译期(而不是链接期)就能发现