C++右值引用 移动语义 完美转发 引用叠加

MyString

浅拷贝与深拷贝

s1先在堆区申请了空间,然后将p指针指向的字符串复制到该堆区空间,
拷贝构造函数是s2指向了s1指向的空间,即指向了同一个堆区空间,会导致函数析构时,会对同一个空间释放两次,这就是浅拷贝
在这里插入图片描述

class MyString
{
private:
	char* str;
public:
	//strlen(p)只算了字符的长度,没有算上'\0'
	MyString(const char* p = nullptr) :str(nullptr)
	{
		if (nullptr != p)
		{
			int len = strlen(p) + 1;
			str = new char[len];
			strcpy(str, p);
			//strcpy_s(str,len,p);  //C11 g++ 不支持
		}
		else
		{
			//相当一个空字符串
			str = new char[1];
			*str = '\0';
		}
	}
	~MyString()
	{
		delete[]str;
		str = nullptr;
	}
	MyString(const MyString& s) :str{s.str} {
		cout << "Copy Create MyString :" << this << endl;
	}
};
int main() {
	MyString s1("yhpinghello");
	MyString s2(s1);
	return 0;
}

所以需要进行深拷贝,s2先申请堆区空间,然后再将原来s1的内容拷贝过去,这样析构s1,s2时就不会相互影响。
在这里插入图片描述

MyString(const MyString& s) :str{nullptr} {
	int len = strlen(s.str) + 1;
	str = new char[len];
	strcpy(str, s.str);
	cout << "Copy Create MyString :" << this << endl;
}

浅赋值与深赋值

MyString& operator=(const MyString& it) {
	this->str = it.str;
	return *this;
}

这个代码有两个问题,第一个是内存泄漏,str指向了it.str,那么str原本指向空间就找不到了,就无法释放了
第二个问题就是,这两个指针指向同一个地方,当调用析构函数释放空间时,当s2释放了他的空间后,s1仍然指向该空间,此时s1就是一个失效指针
浅赋值
在这里插入图片描述
深赋值
就是把s2指向的堆区资源释放,重新开辟一个和s1同样大小的堆区空间,再把s1内容拷贝过来
深赋值和深拷贝构造,的区别就是,多了一步,释放以前的资源

MyString& operator=(const MyString& it) {
	if (this != &it) {
		delete[]this->str;
		int len = strlen(it.str) + 1;
		str = new char[len];
		strcpy(str, it.str);
	}
	return *this;
}

在这里插入图片描述

在这里插入图片描述

按照值返回,会构建将亡值对象,调用拷贝构造函数,深拷贝,回到主函数,调用深赋值函数,这个程序看,堆区开辟了三次,后文用移动拷贝,移动赋值优化。

MyString func(const char* p) {
	MyString tmp(p);
	return tmp;
}
int main() {
	
	MyString s1("tulun");
	s1=func("yhpinghello");
	cout << s1 << endl;
	return 0;
}

在这里插入图片描述
如果用引用返回的话,不构建将亡值,实质上就是将tmp的地址,用eax存着,再析构func,然后回到主函数,就是解引用了一次,将tmp深赋值给s1,但是tmp已经被释放了,从已经死亡的对象捞数据,这是不允许的

MyString& func(const char* p) {
	MyString tmp(p);
	return tmp;
}
int main() {
	
	MyString s1("tulun");
	s1=func("yhpinghello");
	cout << s1 << endl;
	return 0;
}

在这里插入图片描述
所以以值返回和引用返回具有巨大的差异,一般不要用引用返回。

左值与右值

左值概念

能取地址的量,对象,为左值
不能取地址为右值

将亡值:计算所产生的临时量,将亡值没有名字,就是右值,如果将亡值有名字就是泛型左值
纯右值:字面常量,由内置类型和指针运算产生的都是纯右值
类型加括号调用构造函数,创建不具名对象,就是右值
在这里插入图片描述
a++ 这是右值 返回的是不具名对象,
++a 这是左值 返回的是a本身

int main() {
	MyString s1("yhping");//Left value
	MyString& rs = s1;//左值引用

	MyString("tulun");//不具名对象,右值
	MyString &&rsa=MyString("tulun");//右值引用,引用不具名对象
	return 0;
}
int main() {
	const int& a = 10;
	//int tmp=10;
	//const int&a=tmp;
	
	//a += 10;//error

	int&& rr = 10;
	//int tmp=10;
	//int&&rr=tmp;
	rr += 10;
	return 0;
}

左值右值与函数的结合

void func(MyString &s){}//left ref
void func(const MyString &cs){}//const left ref
void func(MyString &&rs){}//right ref
int main(){
	MyString s1("yhping");//Left value
	func(s1);
	return 0;
}

func(s1);中s1是一个左值,首先匹配void func(MyString &s){},没有该引用,退而求其次,匹配void func(const MyString &cs){},如果没有,就会报错,因为不能用s1去初始化右值引用

func(MyString("tulun"));

这里优先和右值引用匹配,没有的话,就和常左值引用匹配,再没有的话,不能匹配左值。

const MyString s1("yhping");//

只能和常性左值匹配,不能和左值匹配,不能和右值匹配

MyString&&rs=MyString("dataprint");
&rs

这里rs是右值引用,rs是不具名对象的别名,rs可以取地址,rs失去右值概念,所以这里变成了左值

常性左值引用是万能引用

移动构造函数

s1不就没有了

MyString(MyString&& s) {//移动拷贝构造
	this->str = s.str;
	s.str = nullptr;
}
int main()
{
	MyString s1("yhping");
	MyString s2(std::move(s1));
	return 0;
}

move的作用就是将左值强转为右值
在这里插入图片描述
将s1的堆区资源移动给了s2

任何时候delete free一个空指针,都是安全的,它会先判断一下

移动赋值函数

问题:s2不就没有了
将堆区的地址移动给s1

MyString& operator=(MyString&& s)
{
	if (this == &s)return *this;
	delete[]str;
	str = s.str;
	s.str = nullptr;
	return *this;
}
int main()
{
	MyString s1("hello");
	MyString s2("yhping");
	s1=std::move(s2);
	//s1=(MyString&&)s2;
	return 0;
}

在这里插入图片描述
如果是深构造和深赋值
在这里插入图片描述
移动构造和移动赋值在实现上的区别还是,赋值需要先释放资源,防止内存泄漏

移动构造和移动赋值的应用

对于没有加static,const修饰的局部对象,return的时候直接将tmp转为右值,调用移动构造,s就是tmp一个别名,移动拷贝构造一个将亡值对象,将tmp的资源转移到将亡值对象,回到主函数,将亡值是右值,调用移动赋值,s就是将亡值的别名
用移动构造和移动赋值,创建对象的次数没有改变,但是对堆区的操作很简答,只用开辟一个空间。

MyString& operator=(MyString&& s)//移动赋值
{
	if (this == &s)return *this;
	delete[]str;
	str = s.str;
	s.str = nullptr;
	return *this;
}
MyString(MyString&& s) {//移动构造
	this->str = s.str;
	s.str = nullptr;
}
MyString func(const char* p) {
	MyString tmp(p);
	return tmp;
}
int main() {
	
	MyString s1("hello");
	s1=func("yhping");
	cout << s1 << endl;
	return 0;
}

在这里插入图片描述

移动语义

move是不能将一个常性左值,转成右值。里面有规则,删除了引用,再进行强转。
move实际上并不能移动任何东西,它唯一的功能是将一个左值强制转换为一个右值,使我们可以通过右值引用使用该值,以用于移动语义。强制转换为右值的目的是为了方便实现移动构造。移动赋值,这样对堆区的开辟销毁的次数就会减小,提高效率

移动语义并不是只针对堆资源,还针对系统资源,
class Timer {
int m_fd;
}
定时器的文件描述符管理的是系统资源。系统资源无法进行拷贝构造,赋值,只能进行移动构造和移动赋值,这样对系统资源的封装更加安全稳定。

using namespace std;
template<class _Ty>
struct my_remove_reference {
	using type = _Ty;
	my_remove_reference() {
		type x;
	}
};
template<class _Ty>
struct my_remove_reference<_Ty&> {
	using type = _Ty;
	my_remove_reference() {
		type x;
	}
};
template<class _Ty>
struct my_remove_reference<_Ty&&> {
	using type = _Ty;
	my_remove_reference() {
		type x;
	}
};

int main() {
	my_remove_reference<int>();
	my_remove_reference<int&>();
	my_remove_reference<int&&>();
}

x 都是整型

template<class _Ty>
using my_remove_reference_t=typename my_remove_reference<_Ty>::type
int main() {
	my_remove_reference_t<int>x;
	my_remove_reference_t<int&>y;
	my_remove_reference_t<int&&>z;
	return 0;
}

xyz都是整型

template<class _Ty>
void fun(_Ty&& t) {
}
int main() {
	int a = 10;
	const int b = 20;
	fun(a);  //   int &
	fun(b);   //const int&
	fun(int(30));//int&&
	return 0;
}

引用型别未定义,是模板的推演规则,不是右值引用,
fun传入什么类型的参数,t就是什么类型的引用

template<class _Ty>
my_remove_reference_t<_Ty>&& my_move(_Ty&& t) {
	return static_cast<my_remove_reference_t<_Ty>&&>(t);
}

int main() {
	MyString s1("yhping");
	const MyString s2("tulun");
	MyString s3, s4;
	s3 = my_move(s1);
	s4 = my_move(s2);
	return 0;
}

s3 = my_move(s1); s1是左值,所以t就是 MyString& 然后删除_Ty的引用型别,static_cast<my_remove_reference_t<_Ty>&&>(t); =》 static_cast<MyString&&>(t);
我们就将左值强转成了右值
s4 = my_move(s2); 转成常性右值,就只能调用深赋值函数,因为常性左值引用是万能引用

如果不用std::move,拷贝的代价很大,性能较低。使用move几乎没有任何代价,只是转换了资源的所有权。实际上是将左值变成右值引用,然后应用move语义调用移动赋值函数,就避免了拷贝,提高了程序性能。当一个对象内部有较大的堆内存或者动态数组时很有必要写move语义的拷贝构造函数和赋值函数,避免无谓的深拷贝,以提高性能。

这里也要注意对move语义的误解,move只是转移了资源的控制权,本质上是将左值强制转换为右值引用,以用于move语义,避免含有资源的对象发生无谓的拷贝。move对于拥有形如对内存、文件句柄等资源的成员的对象有效。如果是一些基本类型,比如int和char[10]数组等,如果使用move,仍然会发生拷贝(因为没有对应的移动构造函数),所以说move对于含资源的对象来说更有意义。

完美转发

void Print(int& a) { cout << "left value ref" << endl; }
void Print(const int& b) { cout << "const left value ref" << endl; }
void Print(int&& c) { cout << "right value ref" << endl; }
template<class _Ty>
void func(_Ty&& t) {
	Print(t);
}
int main() {
	int a = 10;
	const int b = 20;
	int&& c = 30;//
	func(a);
	func(b);
	func(c);
	func(40);
	return 0;
}

c本来是右值引用,但是具名后,变成左值,当将亡值一旦具名变成了左值,因此我们要求在程序的连续调用过程中,值的类型不可变,所以就有完美转发的概念
在这里插入图片描述

template<class _Ty>
void func(_Ty&& t) {
	Print(forward<_Ty>(t));
}

在这里插入图片描述所谓完美转发(Perfect Forwarding),是指在函数模板中,不管参数是T&&这种未定的引用还是明确的左值引用或者右值引用,它会按照参数本来的值类型转发。


void Print(int& a) { cout << "left value ref" << endl; }
void Print(const int& b) { cout << "const left value ref" << endl; }
void Print(int&& c) { cout << "right value ref" << endl; }
template<class _Ty>
void func(_Ty&& t) {
	Print(std::forward<_Ty>(t));
}
int main() {
	int a = 10;
	const int b = 20;
	int&& c = 30;//
	&a;
	&b;
	&c;//c右值引用 具名 变成了左值 可以取地址
	Print(a);
	Print(b);
	Print(c);
	Print(forward<int>(c));//本来是右值。
	return 0;
}

在这里插入图片描述

引用叠加

template<class _Ty>
struct remove_reference {
	using type = _Ty;
	using _Const_thru_ref_type = const _Ty;
};
template<class _Ty>
struct remove_reference<_Ty&> {
	using type = _Ty;
	using _Const_thru_ref_type = const _Ty&;
};
template<class _Ty>
struct remove_reference<_Ty&&> {
	using type = _Ty;
	using _Const_thru_ref_type = const _Ty&&;
};
template<class _Ty>
using remove_reference_t = typename remove_reference<_Ty>::type;

template<class _Ty>
remove_reference_t<_Ty>&& move(_Ty&& _Arg)noexcept {
	return static_cast<remove_reference_t<_Ty> && (_Arg);
}

template<class _Ty>
_Ty&& my_forward(remove_reference_t<_Ty>& _Arg)noexcept {
	return static_cast<_Ty&&>(_Arg);
}
template<class _Ty>
_Ty&& my_forward(remove_reference_t<_Ty>&& _Arg)noexcept {
	return static_cast<_Ty&&>(_Arg);
}
void Print(int& a) { cout << "left value ref" << endl; }
void Print(const int& b) { cout << "const left value ref" << endl; }
void Print(int&& c) { cout << "right value ref" << endl; }
template<class _Ty>
void func(_Ty&& t) {
	Print(my_forward<_Ty>(t));
}
int main() {
	int a = 10;
	const int b = 20;
	int&& c = 30;
	func(a);
	func(b);
	func(c);
	func(40);
	return 0;
}

func(a); _Arg => int& _Ty => int&
_Ty&& my_forward(remove_reference_t<_Ty>& _Arg)noexcept 删除_Ty的引用型别
将t给_Arg _Ty int& && 左值引用叠加右值引用仍然是左值引用 最后返回_Ty&& 继续叠加,就是左值引用 最后返回左值引用,调用void Print(int& a)

func(40); , _Ty是整型 _Arg 也是int _Ty&& 就是右值引用 返回叠加也是右值引用 最后_Ty&& 最后将_Arg 强转成右值引用,调用void Print(int&& c)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值