1. 拷贝函数
Example(const Example&);
Example& operator=(const Example&);
2. 阻止拷贝
有些类不允许拷贝,比如iostream类和unique_ptr智能指针。阻止拷贝通过拷贝函数实现。有下面3种方法:
1. 定义删除的拷贝函数
有些类不能有拷贝构造函数、拷贝赋值函数,比如互斥锁。
class Example {
public:
// 构造函数。
Example() = default;
// 定义删除的拷贝构造函数和拷贝赋值函数。
Example(const Example&) = delete;
Example& operator=(const Example&) = delete;
}
2. 声明拷贝函数为private的,且不定义它们
C++旧标准不支持定义删除的函数时,通过这种方法阻止拷贝。
class Example {
Example(const Example&);
Example& operator=(const Example&);
public:
// 构造函数。
Example() = default;
}
只声明不定义,使得成员函数或友元函数也不能调用拷贝函数,否则会出现链接错误。
3. 编译器合成删除的默认函数
默认函数:如果用户不自定义,编译器会自动合成默认构造函数、默认拷贝函数、默认移动函数、默认析构函数。
编译器合成删除的函数的情况如下:
- 如果某个成员的析构函数不可访问,则编译器合成的析构函数是删除的。
- 如果某个成员的拷贝构造函数 / 析构函数不可访问,则编译器合成的拷贝构造函数是删除的。
- 如果某个成员是const / 引用 / 拷贝赋值函数不可访问,则编译器合成的拷贝赋值函数是删除的。
- 如果某个成员是const且无类内初始化且未显式定义默认构造函数 / 引用且无类内初始化 / 析构函数不可访问,则编译器合成的默认构造函数是删除的。
不可访问指函数是私有的或删除的。成员指数据成员、类成员、基类。
3. 对象移动
1. 右值引用
右值要么是字面常量,要么是在表达式求值过程中创建的临时对象。
右值引用就是必须绑定到右值的引用。
2. std::move函数
std::move将一个左值转换为对应的右值引用类型。它的实现:
template <typename T>
typename remove_reference<T>::type&& move(T&& t) {
return static_cast<typename remove_reference<T>::type&&>(t);
}
也就是说std::move是用static_cast实现的。下面两行等价:
int&& r = move(1);
int&& r = static_cast<int&&>(1);
3. 参数匹配
void fun(int&); // 函数1。
void fun(int&&); // 函数2。
void fun(const int&); // 函数3。
void fun(const int&&); // 函数4。
字面值常量的匹配顺序:2、4、3。
变量(普通变量、左值引用变量、右值引用变量)的匹配顺序:1、3。
std::move(变量或字面值常量)的匹配顺序:2、4、3。
常量(普通常量、左值引用常量、右值引用常量)的匹配顺序:3。
std::move(常量)的匹配顺序:4、3。
4. 对象移动
移动函数与拷贝函数的声明区别:移动函数的参数是非const类型的右值引用;拷贝函数的参数是const类型的左值引用。
Example(Example&&);
Example& operator=(Example&&);
由参数匹配知,使用std::move时,会优先匹配移动函数,没有移动函数时会匹配形参为const类型的拷贝函数。