拷贝控制

当定义一个类时,我们显示地或隐世地指定在此类型的对象拷贝、移动、赋值和销毁时做什么。一个类通过定义五种特殊的成员函数来控制这些操作,包括:拷贝构造函数拷贝赋值运算符移动构造函数移动赋值运算符析构函数。以上操作为拷贝控制操作。

class Foo{
friend void swap(Foo&,Foo*);//将swap定义为友元函数,并在外部重载
public:
	Foo();//默认构造函数
	//或者Foo()=default;显示要求生成合成版本
	Foo(const Foo&);//拷贝构造函数
	Foo& operator=(const Foo&);//重载的拷贝赋值运算符
	~Foo();//析构函数
	//或者~Foo()=default;显示要求生成合成版本
private:
	string* s;
};

拷贝构造函数

  • 在几种情况下都会被隐式使用,因此通常拷贝构造函数不应该是explicit
  • 若一个类有一个移动构造函数,则拷贝初始化有时会使用移动构造函数而非拷贝构造函数来完成。

构造函数

  • 成员初始化是在函数体执行之前完成的,且按照他们在类中出现的顺序进行初始化。

拷贝赋值运算符

  • 如果将一个对象赋予它自身,赋值运算符必须能正常工作
  • 大多数赋值运算符组合了析构函数和拷贝构造函数的工作
  • 要考虑其异常安全的,即当异常发生时,能将左侧运算对象置于一个有意义的状态
//其主要针对防止内存不够用时,依旧能够保持左侧运算对象的原始状态
Foo& Foo::operator=(const Foo& tmp){
	//if (this!=&tmp){
	//阻止对自身的赋值
		auto newS = new string(*tmp.s);//拷贝底层string
		delete s;//释放旧内存
		s=newS;//从右侧运算对象拷贝数据到本对象
		return *this;//返回本对象
	//}
}

析构函数

  • 由于析构函数不接受参数,因此它不能被重载。对于一个定类,只会有唯一一个析构函数。
  • 首先执行函数体,然后销毁成员。成员按初始化顺序的逆序销毁。(与构造时相反
  • 由于智能指针是类类型,所以具有析构函数,因此,与普通指针不同,智能指针成员在析构阶段会被自动销毁。
  • 析构函数会自动运行

大多数类应该定义默认构造函数、拷贝构造函数和拷贝赋值运算符,无论是显示地还是隐式地。

阻止拷贝
通过将拷贝构造函数和拷贝赋值运算符定义为删除函数来组织拷贝,即声明了他们,但不能以任何方式使用:

struct NoCopy{
	NoCopy()=default;//使用合成的默认构造函数
	NoCopy(const NoCopy&)=delete;//阻止拷贝
	NoCopy&operator=(const NoCopy&)=delete;//阻止赋值
	~NoCopy()=default;//使用合成的析构函数
};
  • =delete必须出现在函数第一次申明的时候,而=default不是。
  • 析构函数不能是删除的成员,除非是动态分配(可是不能删除)

交换函数swap

  • 对于指针,交换指针而非指向的对象

移动构造函数和std::move

  • 有些标准库类,包括string,都定义了所谓的”移动构造函数“,但是关于string的移动构造函数如何工作和实现的细节都尚未公开。
  • 移动构造函数通常是将资源从给定对象”移动“而不是拷贝到正在创建的对象,且保证原始的string仍然保持一个有效的、可析构的状态。
  • move定义在utility头文件中。
  • 若对象在拷贝后立即就被销毁,则移动而非拷贝对象会大幅度提升性能
  • IO类或unique_ptr这样的类,都包含不能被共享的资源(如指针或IO缓冲),因此

通过调用move函数来获得绑定到左值上的右值引用,其相当于告诉编译器,我们有一个左值,但是我们希望像一个右值一样处理。这意味着,除了对rr1赋值或销毁外,将不再使用它

int &&rr3 = std::move(rr1);//ok
StrVec r = std::move(rO);//rO为原始的StrVec对象,且StrVec定义了移动构造函数

使用std::move而非move可以避免潜在的名字冲突。

右值引用
为了支持移动操作而引入,通过&&来获得右值引用。
性质:只能绑定到一个将要销毁的对象
与左值引用&相反,能够将右值引用绑定到要求转换的表达式、字面常量或是返回右值的表达式上,但不能绑定到一个左值上:

int i=42;
int &r = i;//ok
int &&rr = i;//no,不能绑定到一个左值上
int &r2 = i*42;//no,i*42是一个右值
const int &r3 = i*42;//ok,可以将一个const引用绑定到一个右值上
int &&rr2 = i*42;//ok

自定义一个移动构造函数

StrVec::StrVec(StrVec&& s) noexcept//移动操作不应抛出任何异常
//成员初始化器接管s中的资源
:elements(s.elements),first_free(s.first_free),cap(s.cap)
{
	//令s进入这样的状态——对其运行析构函数是安全的
	s.elements = s.first_free=s.cap=nullptr;
}
  • 类似拷贝构造函数,移动构造函数的第一个参数是该类类型的一个引用。任何额外的参数必须有默认实参。
  • 不同于拷贝构造函数,这个引用参数在移动构造函数中是一个右值引用。且不分配任何新的内存,在接管完内存后,需要将源对象的指针都置为nullptr
  • 除了完成资源移动,移动构造函数还需确保移后源对象处于这样一个状态——销毁它是无害的。特别是,一旦资源完成移动,源对象必须不再指向被移动的资源——这些资源的所有权已经归属新创建的对象。

移动赋值运算符

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;
}

只有当一个类没有定义任何自己版本的拷贝控制成员,且它的所有数据成员都能移动构造或移动赋值时,编译其才会为它合成移动构造函数或移动赋值运算符
否则,合成移动构造函数或移动赋值运算符是删除函数,阻止调用

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值