类设计者的工具

《C++ Primer》第三部分

Posted by ZhouSh on May 24, 2023

十三、拷贝控制

拷贝构造函数的第一个参数是自身类类型的引用(最好是const &),且任何额外参数都有默认值。Foo(const Foo&)。它的作用是用一个对象构造新的对象。如果我们没有为一个类定义拷贝构造函数,编译器会自动定义一个。

如果一个类需要自定义析构函数,几乎可以肯定它也需要自定义拷贝赋值运算符拷贝构造函数

如果一个类需要一个拷贝构造函数,几乎可以肯定它也需要一个拷贝赋值运算符。反之亦然。

我们可以通过将拷贝控制成员定义为=default来显式地要求编译器生成合成的版本。

我们可以通过在函数的参数列表后面加上=delete将拷贝构造函数和拷贝赋值运算符定义为删除的函数(deleted function)来阻止拷贝。即虽然声明了它们,但不能以任何方式使用它们。

析构函数不能是删除的成员,否则无法销毁此类型的对象。

对于有引用成员的类,合成拷贝赋值运算符应该被定义为删除的。因为虽然我们可以将一个新值赋予一个引用成员,但这样做改变的是引用指向的对象的值,而不是引用本身。

IO类或unique_ptr这样的类包含不能被共享的资源(如指针或IO缓冲)。因此,这些类型的对象不能拷贝但可以移动

为了支持移动操作,新标准引入了右值引用(rvalue reference)。所谓右值引用就是必须绑定到右值的引用。通过&&而不是&来获得右值引用。右值引用有一个重要的性质——只能绑定到一个将要销毁的对象

一般而言,一个左值表达式表示的是一个对象的名字,而一个右值表达式表示的是对象的

我们不能将左值引用绑定到要求转换的表达式、字面常量或是返回右值的表达式。右值引用有着完全相反的绑定特性:我们可以将一个右值引用绑定到这类表达式上,但不能将一个右值引用直接绑定到一个左值上。

左值持久,右值短暂。

右值引用指向将要被销毁的对象。因此,我们可以从绑定到右值引用的对象“窃取”状态。

std::move可以获得绑定到左值上的右值引用。move调用告诉编译器:有一个左值,但希望像一个右值一样处理它。在调用move之后,我们不能对移后源对象的值做任何假设,可以销毁一个移后源对象,也可以赋予它新值,但不能使用一个移后源对象的值。

1
2
3
int &&rr1 = 42; // 正确,字面值常量是右值
int &&rr2 = rr1; // 错误,表达式rr1是左值
int &&rr3 = std::move(rr1) // 正确,

移动构造函数:Foo(Foo&&) noexcept;,有2个引用符号。由于移动操作“窃取”资源,通常不分配任何资源。因此移动操作通常不会抛出任何异常。所以需要标记noexcept通知标准库该函数不抛出异常,否则会为了处理可能的异常而做一些额外的工作。