C++Primer笔记——拷贝控制


定义一个类时,五种特殊的成员函数( 拷贝控制操作):
拷贝构造函数
拷贝赋值运算符
移动构造函数
移动赋值运算符
析构函数

13.1 拷贝、赋值与销毁

13.1.1 拷贝构造函数

class A{
public:
	A();//默认构造函数
	A(const A&); //拷贝构造函数
	
}

拷贝构造函数的第一个参数必须是一个引用类型,拷贝构造函数会被隐式地使用因此不时explicit

合成拷贝构造函数

未显式定义拷贝构造函数时,编译器自动定义的拷贝构造函数。
合成拷贝函数将给定对象中的每一个非static成员拷贝到正在创建的对象中:

  • 类类型: 使用其拷贝构造函数;
  • 内置类型: 直接拷贝;
  • 数组类型: 逐个元素地拷贝一个数组类型的成员,依据数组元素的类型进行对应的拷贝操作;

拷贝初始化

拷贝初始化使用拷贝构造函数或移动构造函数进行。

  • 使用=定义变量
  • 将一个对象作为实参传给一个非引用类型的形参
  • 从一个返回类型的函数返回一个对象
  • 用花括号列表初始化一个数组中的元素或一个聚合类的成员

注:容器的push, insert 使用拷贝初始化。emplace使用直接初始化。

参数和返回值

拷贝构造函数必须使用引用类型作为参数,非引用类型的形参需要调用拷贝构造函数来拷贝实参,造成无限循环。

拷贝初始化的限制
如果初始化值要求通过一个explicit的构造函数来进行类型转换,就不可以使用拷贝初始化。
编译器可以(但不是必须)跳过拷贝/移动构造函数。

13.1.2 拷贝赋值运算符

赋值运算符 “=”

重载赋值运算符

class A{
public:
	A& operator=(const A&);//赋值运算符通常应该返回一个指向其左侧运算对象的引用。
};

合成拷贝赋值运算符与合成拷贝构造函数的执行过程类似。

13.1.3 析构函数

析构函数释放对象使用的资源,并销毁对象的非static数据成员。

class A{
public:
	~A();      //析构函数无法重载,一个类只有唯一一个析构函数 
};

一个析构函数中,首先执行函数体,然后销毁成员,成员按初始化逆序销毁。

调用时机

当对象被销毁时:

  • 变量离开其作用域时;
  • 当一个对象被销毁时,它的成员也会被销毁;
  • 容器被销毁时,它的元素也会被销毁;
  • 对于动态分配的对象,当对他的指针应用delete运算符删除时;
  • 临时对象,创建它的完整表达式结束时被销毁。

合成析构函数
析构函数体自身并不直接销毁成员,析构函数体之后隐含的析构阶段销毁成员。

13.1.4 三/五法则

1. 需要析构函数的类也需要拷贝和赋值操作。
2. 需要拷贝操作的类也需要赋值操作,反之亦然。

13.1.5 使用=default

使用=default关键字来显式地要求编译器生成合成的版本。

class A{
public:
	A()=default;
	A(const A&)=default;
	A& operator=(const A&)=default;
	~A()=default;      //析构函数无法重载,一个类只有唯一一个析构函数 
};

13.1.6 阻止拷贝

C++ 11 可以使用=delete关键字将拷贝构造函数以及拷贝赋值运算符定义为删除的函数来阻止拷贝。

class A{
public:
	A()=default;
	A(const A&)=delete;
	A& operator=(const A&)=delete;
	~A()=default;      //析构函数无法重载,一个类只有唯一一个析构函数 
};

=delete必须出现在函数第一次声明的时候。=default不用。在类声明内使用=default默认为内联函数,在外联则在类外使用。
注意:
析构函数不能是删除的成员
(不可以使用=delete显式声明,但可以因为不可访问或者数据成员类型的析构函数是删除的,被编译器定义为删除的)
如果析构函数被删除,则无法定义该类型的变量或创建临时对象。
但是可以动态分配这种类型的对象,但是不能使用delete释放这些对象。

合成的拷贝控制成员可能是删除的
规则总结:如果类的某个数据成员不能默认构造、拷贝、赋值或销毁 ,则对应的合成版本的成员函数将被编译器定义为delete。

private拷贝控制
将拷贝控制的成员函数声明为private可以避免用户拷贝这个类型的对象,但是友元和成员函数可以拷贝对象,将这些拷贝控制成员声明为private且不定义,则可以阻止友元和成员函数进行拷贝。

13.2 拷贝控制和资源管理

定义拷贝控制成员:

  • 行为像值的类:每个对象有自己的状态,拷贝时副本与原对象完全独立,改变副本对原对象无影响;
  • 行为像指针的类:共享状态,拷贝时,副本与原对象使用相同的底层数据,改变副本也会改变原对象。

13.2.1 行为像值的类

拷贝构造函数:使用new运算符分配新的内存空间
析构函数:释放对象占用的内存
拷贝赋值运算符:组合析构函数的操作释放左侧对象原先的内存,以及拷贝构造函数的操作拷贝控制右侧运算对象的数据。

13.2.2 定义行为像指针的类

构造函数: 初始化对象,创建引用计数。
拷贝构造函数:拷贝给定对象的数据成员,包括引用计数。
析构函数:递减计数器,如果计数器为0,则释放状态。
拷贝赋值运算符:递增右侧计数器,递减左侧运算对象的计数器。如果左侧对象计数器为0则释放左侧对象内存。

管理相同内存的不同对象如何更新计数器:创建一个对象时动态分配一个新的计数器,当拷贝或赋值时,拷贝指向计数器的指针,则副本和原对象都会指向相同的计数器。

13.3 交换操作

void swap(A& a1,A& a2){
	//成员逐个按照对应的方式进行交换
}
//通常在类域内声明为inline 友元形式

class A{
	friend void swap(A&,A&);
};
inline void swap(A &la,A &ra){
	//...
}

定义了swap的类通常在拷贝赋值运算符的定义中使用swap操作。

13.4 拷贝控制示例

Message和Folder类

13.5 动态内存管理类

StrVec类

13.6 对象移动

13.6.1 右值引用

新标准中的全新引用类型:右值引用,指必须绑定到右值的引用。通过&&获得,且只能绑定到一个将要销毁的对象。

int i=42;
int &r=i;
int &&rr=42;
int &r2=i*42;//错误,左值引用不可以绑定到一个返回右值的表达式上。
const int & r3=i*42; //正确,常量左值引用可以绑定到一个右值上。
int &&rr2 =i*42; //正确,右值引用绑定到右值上。

注意: 变量都是左值(可以看作一个只有运算对象没有运算符的表达式)因此不可以讲一个右值引用绑定到一个右值引用类型的变量上。

标准库move函数:

#include <utility>
int &rr3=std::move(rr1); //将左值rr1转换为右值引用类型

13.6.2 移动构造函数和移动赋值运算符

移动构造函数

A::A(A&& a) noexcept://初始值列表
{
	//函数体
}

移动构造函数第一个参数是该类类型的一个右值引用,任何额外的参数都必须有默认实参。
完成资源移动后,移动构造函数需要保证销毁移后源对象是无害的。源对象必须不再指向被移动的资源。
移动构造函数不分配任何新内存:
1.接管源对象内存
2.源对象指针置为nullptr
3.销毁源对象

移动操作、标准库容器与异常
移动操作通常不会抛出异常,使用noexcept关键字标记(新标准)

移动赋值运算符
与移动构造函数相同的工作.

A& A::operator=(A &&a) noexcept{
	if(this != &a){//如果不是自赋值
		//释放左侧对象已有元素
		//接管右侧对象资源
		//将右侧对象置于可析构函数状态
	}
}

注意 移后源对象必须可析构!

合成的移动操作
只有当一个类没有定义任何自己版本的拷贝控制成员,且类的每个非static数据成员都可以移动时,编译器才会自动合成移动构造函数和移动赋值运算符;内置类型成员都可移动,类类型成员有对应的移动操作则可以移动。

移动操作被定义为删除的,when:
有类成员定义了拷贝构造函数且没定义移动构造函数,或未定义拷贝构造函数且编译器不能合成移动构造函数;
有类成员的移动构造函数或移动赋值运算符被定义为删除的或不可访问的;
有类成员的析构函数被定义为删除的或不可访问的;
有类成员是const或引用。

移动右值,拷贝左值; 如果没有移动构造函数,右值也拷贝。

**三/五法则:**如果一个类定义了任何一个拷贝操作,它就应该定义全部五个操作。

移动迭代器

make_move_iterator(it) //将迭代器it转化为移动迭代器

移动迭代器解引用后获得右值引用。

13.6.3 右值引用和成员函数

引用限定符 & 与 &&
引用限定符纸能用于非static成员函数,且同出现在函数的

class A{
	A& operator=(const A&) &; //指出this的左值属性,如果&&则表示this的右值属性。
	A anotherFunc() const &; //const限定符必须在前面
};
A& A::operator=(const A& rhs) &{
	//。。。
}

引用限定符也可以区分函数的重载版本,两个不同引用限定符的重载函数(相同函数名相同参数列表)必须都加上引用限定符。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值