C++模板编程特性之移动语义

 更新时间:2022年08月23日 15:28:36   作者:努力的张张  
首先,移动语义和完美转发这两个概念是在C++的模板编程的基础上,新增的特性,主要是配合模板来使用。本篇会从C++的值类型,到移动拷贝与移动赋值来理解移动语义与完美转发

C++的值类型

我们知道,每个变量都有类型,或整形或字符型等来进行了分类,不仅如此,C++表达式(带有操作数的操作符、字面量、变量名等)在类型的属性上,还有一种属性,即值类别(value category)。且每个表达式只属于三种基本值尖别中的一种:左值(lvalue),右值(rvalue),将亡值(xvalue),每个值类别都与某种引用类型对应。

其中,左值和将亡值成为泛左值(generalized value,gvalue),纯右值和将亡值合称为右值(right value,rvalue)。

一般我们讲,左值就是可以取地址的,具有名字的,比如 int a; a是变量的名字,&a是变量的地址,a就是左值。那么右值呢,自然就是不可以取地址的,比如int b=10; 而这个10就是一个右值,在内存中不会分配有地址,自然也不能取地址。

将亡值,则是指在调用某个函数退出返回时,如果函数有返回值,那么就会有将亡值的存在,为什么称之为将亡值,就是说这个值在函数作用域创建,但由于函数返回结束,局部变量都会销毁,故会产生一个将亡值来接收这个值,完成赋值的任务。

从上图也可以看出,将亡值既可能转为左值,也可能成为右值,那么关键就在于要看是否具有名字了。

下面看这样一段程序:

#include<iostream>
#include<type_traits>
using namespace std;
class MyString
{
private:
	char* str; // heap;
public:
	MyString(const char* p = nullptr) :str(nullptr)
	{
		if (p != nullptr)
		{
			int n = strlen(p) + 1;
			str = new char[n];
			strcpy_s(str, n, p);
		}
		cout << "Create MyString: " << this << endl;
	}
	MyString(const MyString& st)
	{
		if(st.str!=NULL)
		str = st.str;
		cout << "Copy Create MyString: " << this << endl;
	}
	MyString& operator=(const MyString& st)
	{
		if (st.str != NULL)
		str = st.str;
		cout << this << " operator=(const MyString &):  " << &st << endl;
		return *this;
	}
	~MyString()
	{
		delete[]str;
		str = nullptr;
		cout << "Destroy MyString : " << this << endl;
	}
	void PrintString() const
	{
		if (str != nullptr)
		{
			cout << str << endl;
		}
	}
};
int main()
{
	MyString *a=new MyString("lisa");
	MyString *b = a;
	delete b;
	a->PrintString();
	return 0;
}

MyString类型成员有指针变量,且采用浅拷贝方式。当程序运行时,可以看到,两个指针指向了同一个地址,此时,若释放了b指针,再以a指针访问指针成员,就会出现问题。

还有,当函数以值类型返回,构造临时对象,若有指针变量,且采用浅拷贝,就会出现多次析构的问题,导致程序崩溃。

当我们将程序都改为深拷贝时,深拷贝又会导致,程序多次骚扰对空间,此时就提出了move语义。

std::move

std::move其实并没有移动任何东西,它唯一的功能是将一个左值强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义。从实现上讲,move基本等同于一个类型转换。

值得注意的是,通过move转化成右值后,被转化的左值的生命周期并没有随着左右值的转化而改变。但通常情况下,我们需要转换成右值引用的还是一个确定生命期即将结束的对象。

右值引用与移动构造和移动赋值

在c++11中增加了右值引用的概念,即对右值的引用,通过右值引用,可以延长右值的生命期。我们都知道左值引用是变量值的别名,那么右值引用则是不具名变量的别名。

右值引用是不能绑定到任何左值的,但有个例外,常量左值是一个万能引用,可以引用任何值,包括右值引用。

class MyString
{
private:
	char* str; // heap;
public:
	MyString(const char* p = nullptr) :str(nullptr)
	{
		if (p != nullptr)
		{
			int n = strlen(p) + 1;
			str = new char[n];
			strcpy_s(str, n, p);
		}
		cout << "Create MyString: " << this << endl;
	}
	MyString(const MyString& st)
	{
		if (st.str != nullptr)
		{
			int n = strlen(st.str) + 1;
			str = new char[n];
			strcpy_s(str, n, st.str);
		}
		cout << "Copy Create MyString: " << this << endl;
	}
	MyString& operator=(const MyString& st)
	{
		if (this != &st && str != st.str)
		{
			delete[]str;
			if (st.str != nullptr)
			{
				int n = strlen(st.str) + 1;
				str = new char[n];
				strcpy_s(str, n, st.str);
			}
		}
		cout << this << " operator=(const MyString &):  " << &st << endl;
		return *this;
	}
	MyString(MyString&& st)
	{
		str = st.str;
		st.str = nullptr;
		cout << "Move Copy Create MyString" << this << endl;
	}
	MyString& operator=(MyString&& st)
	{
		if (this == &st) return *this;
		if (this->str == st.str)
		{
			st.str = nullptr;
			return *this;
		}
		delete[]str;
		str = st.str;
		st.str = nullptr;
		cout << "Move operator=(MyString &&)" << endl;
		return *this;
	}
	~MyString()
	{
		delete[]str;
		str = nullptr;
		cout << "Destroy MyString : " << this << endl;
	}
	void PrintString() const
	{
		if (str != nullptr)
		{
			cout << str << endl;
		}
	}
};
int main()
{
	const MyString stra("hello");
	MyString strb;
	strb = std::move(stra);//调用普通的赋值方法
	strb.PrintString();
	return 0;
}

这里的move还是调用普通的赋值函数,并未做到真正的资源转移,但是若写成如下结构:

int main()
{
	const MyString stra("hello");
	MyString strb;
	//strb = std::move(stra);//调用普通的赋值方法
	strb = (MyString&&)stra;
	strb.PrintString();
	return 0;
}

通过右值引用,可以延长右值的生命期。从而,有了右值引用出现,这个时候配合移动构造与移动赋值,就可以完成资源转移了。

然后,我们再看一个例子:

MyString& fun()
{
	MyString st=("newdata");
	return st;//xvalue
}
int main()
{
	MyString("zhangsan").PrintString();
	const MyString& a = fun();
	a.PrintString();
	MyString& b = fun();
	b.PrintString();
	return 0;
}

在程序运行时,会发现程序崩溃了,原因是:

函数中返回局部对象的引用,因为函数调用结束会销毁局部对象,而引用则就成为了非法的访问。因为不要在函数中返回局部对象的引用。

若我们将fun()函数的返回改为右值引用呢?

MyString&& fun()
{
	return MyString("newdata");
}
int main()
{
	MyString("zhangsan").PrintString();
	const MyString& a = fun();//x
	a.PrintString();
	//MyString& b = fun();
	//b.PrintString();
	MyString&& c = fun();//x
	c.PrintString();
	MyString&& d = c;//error
	return 0;
}

将亡值回去的时候,就得看看有没有具名,一旦具名就是左值了,否则是右值

可以发现,右值引用是不具名的,但是右值引用本身却是个左值,经过右值引用b接收后,就已经变成了左值,具有了名字。

到此这篇关于C++模板编程特性之移动语义的文章就介绍到这了,更多相关C++移动语义内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++11系列学习之可调用对象包装器和绑定器

    C++11系列学习之可调用对象包装器和绑定器

    这篇文章主要介绍了C++11系列学习之可调用对象包装器和绑定器,下文基于C++的相关资料展开详细内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-04-04
  • 详解C语言中return与exit的区别

    详解C语言中return与exit的区别

    这篇文章主要介绍了详解C语言中return与exit的区别的相关资料,希望通过本文能帮助到大家,让大家理解这部分内容,需要的朋友可以参考下
    2017-10-10
  • C语言中getchar()函数的用法小结

    C语言中getchar()函数的用法小结

    这篇文章主要介绍了C语言中getchar()函数的用法,getchar是输入函数,输入的过程是什么呢,本文给大家详细讲解,对C语言getchar()函数相关知识感兴趣的朋友一起看看吧
    2022-10-10
  • LintCode 堆化详解及实例代码

    LintCode 堆化详解及实例代码

    这篇文章主要介绍了LintCode 堆化详解及实例代码的相关资料,需要的朋友可以参考下
    2017-04-04
  • 深入分析C++中执行多个exe文件方法的批处理代码介绍

    深入分析C++中执行多个exe文件方法的批处理代码介绍

    本篇文章是对C++中执行多个exe文件方法的批处理代码进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • C++中auto类型说明符详解(附易错实例)

    C++中auto类型说明符详解(附易错实例)

    这篇文章主要给大家介绍了关于C++中auto类型说明符的相关资料,文中还附易错实例,在C++11中引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型,需要的朋友可以参考下
    2023-07-07
  • VS2019中QT连接及使用的方法步骤

    VS2019中QT连接及使用的方法步骤

    这篇文章主要介绍了VS2019中QT连接及使用的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • 关于C语言和命令行之间的交互问题

    关于C语言和命令行之间的交互问题

    这篇文章主要介绍了C语言和命令行之间的交互,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-07-07
  • 一篇文章了解c++中的new和delete

    一篇文章了解c++中的new和delete

    C语言提供了malloc和free两个系统函数,完成对堆内存的申请和释放,而C++则提供了两个关键字new和delete,下面这篇文章主要给大家介绍了如何通过一篇文章了解c++中new和delete的相关资料,需要的朋友可以参考下
    2021-12-12
  • C++ float转std::string 小数位数控制问题

    C++ float转std::string 小数位数控制问题

    这篇文章主要介绍了C++ float转std::string 小数位数控制问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11

最新评论