标签: C++Primer 学习记录 拷贝控制
=
定义变量=default
来显示要求编译器生成合成的版本,不过只能对具有合成版本的成员函数使用。当在类内用=default
修饰成员的声明时,合成的函数将隐式地声明为内敛的。如果不希望合成的成员时内联函数,只对成员的类外定义使用 =default
。
class A {
public:
// 拷贝控制成员,使用 default
A() = default;
A(const A&) = default;
A& operator=(const A&);
~A() = default;
};
A& A::operator=(const A&) = default;
=delete
来指出我们希望将它定义为删除的,阻止这个函数的调用。=delete
。如果一个类或其类成员的析构函数被删除,就无法销毁此类型的对象,编译器将不允许定义该类型的变量或创建该类型的临时变量。虽然不能定义这种类型的变量或成员,但可以动态分配这种类型的对象,只是不能释放这些对象!struct NoDtor {
NoDtor() = default;
~NoDtor() = delete; // 不能销毁 NoDtor类型的对象
};
NoDtor nd; // 错误,析构函数是删除的
NoDtor *p = new NoDtor(); // 正确,但是不能 delete p
delete p; // 错误
class HasPtr { public: HasPtr(const std::string &str = std::string()) : ps(new std::string(str)), i(0) {} // 对 ps指向的 string,每个 HasPtr对象都有自己的拷贝 HasPtr(const HasPtr &p) : ps(new std::string(*p.ps)), i(p.i) {} // 拷贝赋值运算符 HasPtr& operator=(const HasPtr &rhs); ~HasPtr() { delete ps; } private: std::string *ps; int i; }; HasPtr& HasPtr::operator=(const HasPtr &rhs) { auto newp = new string(*rhs.ps); // 先拷贝右侧对象 delete ps; // 释放左侧内存 i = rhs.i; // 更新左侧对象 ps = newp; return *this; // 返回 *this }
class HasPtr { public: // 构造函数分配新的 string值、新的初值为 1的计数器 // 计数器也必须使用动态内存,以保证不同对象中的 *use是同一个对象 HasPtr(const std::string &str = std::string()) : ps(new std::string(str)), i(0), use(new std::size_t(1)) {} // 拷贝构造函数,传递计数器指针,递增右侧对象的计数器 HasPtr(const HasPtr &rhs) : ps(new std::string(*rhs.ps)), i(rhs.i), use(rhs.use) { ++*rhs.use; } // 拷贝赋值运算符 HasPtr& operator=(const HasPtr &rhs); // 析构函数,递减计数器,当计数器变为 0时,释放 ps和 计数器指针所指向的内存 ~HasPtr() { if (--*use == 0) { delete ps; delete use; } } private: std::string *ps; int i; std::size_t *use; // 计数器,记录有多少个对象共享 *ps成员 }; HasPtr& HasPtr::operator=(const HasPtr &rhs) { // 先递增右侧运算对象的引用计数,再递减左侧的引用计数,来处理自赋值的情况 ++*rhs.use; if (--*use == 0) { delete ps; delete use; } ps = rhs.ps; i = rhs.i; use = rhs.use; return *this; }
HasPtr temp = v1; // 创建 v1的值的一个临时副本 v1 = v2; // 将 v2的值赋予 v1 v2 = tmp; // 将保存的 v1的值赋予 v2
string *temp = v1.ps; v1.ps = v2.ps; v2.ps = temp;
swap()
调用方式,由于普通函数的优先级高于模板函数,所以会优先调用类版本的 swap函数。而如果该类对象没有定义 swap函数,则会调用标准库中的模板函数。
move
函数来显式地将一个左值转换为对应的右值引用类型。在对一个对象使用 move函数后,可以对这个移后源对象进行销毁或赋值操作,但不能再使用它!使用 move的代码应该使用 std::move,而不是 move,这是因为具有转换为右值引用功能的函数就是标准库中的函数模板,而不使用 std,则可能引起潜在的名字冲突。
noexcept
。StrVec& StrVec::operator=(StrVec &&rhs) noexcept // 移动操作不应抛出任何异常
// 直接检查自赋值
if (this != &rhs)
{
free(); // 释放左侧内存
elements = rhs.elements; // 从 rhs接管资源
first_free = rhs.first_free;
cap = rhs.cap;
// 将 rhs置于可析构的状态
rhs.elements = rhs.first_free = rhs.cap = nullptr;
}
return *this;
=default
的移动操作,又不满足编译器合成移动操作的条件,编译器根本就不会合成它们。而如果用=default
显式要求编译器生成移动操作,且编译器不能移动所有成员,则编译器会将移动操作定义为删除的函数。
// 假定 Y是一个类,它定义了自己的拷贝构造函数但未定义自己的移动构造函数
struct hasY {
hasY() = default;
hasY(hasY &&) = default;
Y men; // hasY将有一个删除的移动构造函数
};
hasY hy, hy2 = std::move(hy); // 错误,移动构造函数是删除的
StrVec v1, v2;
v1 = v2; // v2是左值,使用拷贝赋值
StrVec getVec(istream &); // getVec返回一个右值
v2 = getVec(cin); // 右侧对象是一个右值,使用移动赋值
class HasPtr {
public:
// 添加的移动构造函数
HasPtr(HasPtr &&p) noexcept : ps(p.ps), i(p.i) {p.ps = 0;}
// 赋值运算符同时支持移动和拷贝操作
HasPtr& operator=(HasPtr rhs)
{
// 交换左侧运算对象和局部变量 rhs的内容
swap(*this, rhs); // rhs现在指向本对象曾经使用的内存
return *this; // rhs被销毁,从而 delete了 rhs中的指针
}
// 其他成员的定义
};
void push_back(const X&); // 拷贝,绑定到任意类型的 X
void push_back(X &&); // 移动,只能绑定到类型 X的可修改的右值
class Foo {
public:
Foo& operator=(const Foo&) &;
};
Foo& Foo::operator=(const Foo rhs&) &
{
// 执行将 rhs赋予本对象所需的工作
...
return *this;
}
Foo& retFoo(); // 返回左值
Foo retVal(); // 返回右值
Foo i, j;
i = j; // 正确,i是左值
retFoo() = j; // 正确,retFoo()返回左值
retVal() = j; // 错误,retVal()返回左值
class Foo {
public:
Foo someMen() & const; // 错误,const限定符必须在前
Foo otherMen() const &; // 正确
};
class Foo {
public:
Foo sorted() &&;
Foo sorted() const; // 错误,必须加上引用限定符
// 定义函数类型的类型别名
using Comp = bool(const int&, const int&);
Foo sorted(Comp*);
Foo sorted(Comp*) const; // 正确,两个版本都没有引用限定符
};