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++移动语义内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深入了解c++11 移动语义与右值引用

    1.移动语义 C++11新标准中一个最主要的特性就是提供了移动而非拷贝对象的能力.如此做的好处就是,在某些情况下,对象拷贝后就立即被销毁了,此时如果移动而非拷贝对象会大幅提升性能.参考如下程序: //moveobj.cpp #include <iostream> #include <vector> using namespace std; class Obj { public: Obj(){cout <<"create obj" << e

  • C++左值与右值,右值引用,移动语义与完美转发详解

    目录 C++——左值与右值.右值引用.移动语义与完美转发 一.左值和右值的定义 二.如何判断一个表达式是左值还是右值(大多数场景) 三.C++右值引用 四.std::move()与移动语义 五. 完美转发 总结 C++——左值与右值.右值引用.移动语义与完美转发 在C++或者C语言中,一个表达式(可以是字面量.变量.对象.函数的返回值等)根据其使用场景不同,分为左值表达式和右值表达式. 一.左值和右值的定义 1.左值的英文为locator value,简写为lvalue,可意为存储在内存中.有明

  • C++学习之移动语义与智能指针详解

    移动语义 1.几个基本概念的理解 (1)可以取地址的是左值,不能取地址的就是右值,右值可能存在寄存器,也可能存在于栈上(短暂存在栈)上 (2)右值包括:临时对象.匿名对象.字面值常量 (3)const 左值引用可以绑定到左值与右值上面,称为万能引用.正因如此,也就无法区分传进来的参数是左值还是右值. const int &ref = a;//const左值引用可以绑定到左值 const int &ref1 = 10;//const左值引用可以绑定到右值 (4)右值引用:只能绑定到右值不能绑

  • C++11中value category(值类别)及move semantics(移动语义)的介绍

    前言 C++11之前value categories只有两类,lvalue和rvalue,在C++11之后出现了新的value categories,即prvalue, glvalue, xvalue.不理解value categories可能会让我们遇到一些坑时不知怎么去修改,所以理解value categories对于写C++的人来说是比较重要的.而理解value categories离不开一个概念--move semantics.了解C++11的人我相信都了解了std::move,右值引用

  • 详解C++11中的右值引用与移动语义

    C++11的一个最主要的特性就是可以移动而非拷贝对象的能力.很多情况都会发生对象的拷贝,有时对象拷贝后就立即销毁,在这些情况下,移动而非拷贝对象会大幅度提升性能. 右值与右值引用 为了支持移动操作,新标准引入了一种新的引用类型--右值引用,就是必须绑定到右值的引用.我们通过&&而不是&来获得右值引用.右值引用一个重要的特性就是只能绑定到将要销毁的对象. 左值和右值是表达式的属性,一些表达式生成或要求左值,而另一些则生成或要求右值.一般而言,一个左值表达式表示的是一个对象的身份,而右

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

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

  • 详解C++ 模板编程

    类型模板 类型模板包括函数模板和类模板,基本上是C++开发人员接触模板编程的起点. 下面代码演示了函数模板和类模板的使用方法: // 函数模板 template<typename T> T add(const T& a, const T& b) { return a + b; } // 类模板 template<typename T> class Point { private: T x[3]; ... }; 类型模板以template开始声明,尖括号内的typen

  • idea 模板编程知识小结

    模板编程是idea的强大功能,也提高了开发人员的编程效率,比如输入main函数: public static void main(String[] args){ } 正常情况下我们需要每个字母挨个输入,但是这样输入太慢了,有了模板编程,我们只需要输入psvm或者main,然后回车,就会输出 public static void main(String[] args){ } ,是不是大大的提高了编码速度.这里对模板编程进行简单的介绍. 一.模板编程简介 模板编程的位置如下图:File-->sett

  • 详解C++模板编程中typename用法

    typename的常规用法 typename在C++类模板或者函数模板中经常使用的关键字,此时作用和class相同,只是定义模板参数:在下面的例子中,该函数实现泛型交换数据,即交换两个数据的内容,数据的类型由_Tp决定. template <typename _Tp> inline void swap(_Tp& __a, _Tp& __b) { _Tp __tmp = __a; __a = __b; __b = __tmp; } typename的第二个用法:修饰类型 限定名和

  • 详解C++编程中表达式的语义与计算顺序

    表达式根据其运算符的优先级和分组来计算. 计算顺序 请看以下示例: // expre_pluslang__pluslang_Order_of_Evaluation.cpp // compile with: /EHsc #include <iostream> using namespace std; int main() { int a = 2, b = 4, c = 9; cout << a + b * c << "\n"; cout <<

  • 一篇文章带你了解C++模板编程详解

    目录 模板初阶 泛型编程 函数模板 函数模板概念 函数模板格式 函数模板的原理 函数模板的实例化 模板参数的匹配原则 类模板 类模板的定义格式 类模板的实例化 总结 模板初阶 泛型编程 在计算机程序设计领域,为了避免因数据类型的不同,而被迫重复编写大量相同业务逻辑的代码,人们发展的泛型及泛型编程技术.什么是泛型呢?实质上就是不使用具体数据类型(例如 int.double.float 等),而是使用一种通用类型来进行程序设计的方法,该方法可以大规模的减少程序代码的编写量,让程序员可以集中精力用于业

  • C++11模板元编程-std::enable_if示例详解

    C++11中引入了std::enable_if函数,函数原型如下: template< bool B, class T = void > struct enable_if; 可能的函数实现: template<bool B, class T = void> struct enable_if {}; template<class T> struct enable_if<true, T> { typedef T type; }; 由上可知,只有当第一个模板参数为

  • ASP编程入门进阶(三):接触脚本程序

    有了前面的html中关于form的强化知识,是不是在想,下面就可以开始ASP之旅呢?未必.一般系统学过ASP的编程人员都会切身体会到:在系统学习ASP的内置对象和内置组件之前,学会脚本语言永远是至关重要的.为什么这么说呢?为什么要学脚本语言呢?ASP和脚本语言又有什么关系呢? 首先说说ASP是贵为何物吧.ASP是Microsoft Active Server Pages的简称,它是一种服务器端脚本环境,可用来创建交互式 Web 页并建立强大的 Web 应用程序.可以表明ASP是一种基于服务器端的

  • 详解Java volatile 内存屏障底层原理语义

    目录 一.volatile关键字介绍及底层原理 1.volatile的特性(内存语义) 2.volatile底层原理 二.volatile--可见性 三.volatile--无法保证原子性 四.volatile--禁止指令重排 1.指令重排 2.as-if-serial语义 五.volatile与内存屏障(Memory Barrier) 1.内存屏障(Memory Barrier) 2.volatile的内存语义实现 六.JMM对volatile的特殊规则定义 一.volatile关键字介绍及底

  • C++泛型编程函(数模板+类模板)

    目录 一.函数模板 1.函数模板介绍 2.函数模板与重载函数的关系 3.函数模板实现机制 二.类模板 1.类模板基本语法 2.类模板内函数的整体布局[分文件使用类模板] 3.类模板的static与模板类的static 4.数组实现万能容器 前言: 由于C++是静态语言,也就是说使用一个数据的时候必须先指定类型,这样的操作在编译后变量的类型是无法轻易改变的,就导致扩展性太差.或者一个函数需要很多次重载的时候,代码显得冗杂,由此产生了C++函数模板. 一.函数模板 1.函数模板介绍 ① 函数模板的产

随机推荐