c/c++c++11新特性:初探右值引用与转移语义(代码片段)

mick_seu mick_seu     2022-12-02     769

关键词:

参考自:右值引用与转移语义(李胜利)


C++11之前,右值是不能被引用的,最大限度就是用常量引用绑定一个右值,如 :

const int& a = 1;

为了与左值引用区分,右值引用 && 表示。如下:

#include <iostream>

void fun(int& i) 
	std::cout << "lvalue:" << i << std::endl;


void fun(int&& i) 
	std::cout << "rvalue:" << i << std::endl;


int main() 
	int a = 0;
	fun(a);
	fun(1);
	return 0;


输出:

lvalue:0
rvalue:1
可以发现,临时变量 1 使用了入参为右值引用的 fun 函数完成了函数调用。


右值引用的出现解决了C++11之前移动对象效率问题。下面用自定义的String类来初识转移语义。首先定义一个不带转移语义的普通类:

#include <iostream>
#include <vector>
#include <string.h>

class String 
	public:
		String() 
			std::cout << "String()" << std::endl;
		;
		String(const char* str) 
			len_ = strlen(str);
			Init(str);
			std::cout << "String(const char*)" << std::endl;
		
		String(const String& str) 
			len_ = str.len_;
			Init(str.data_);
			std::cout << "String(const String&)" << std::endl;
		
		String& operator= (const String& str) 
			if (this != &str) 
				len_ = str.len_;
				delete data_;
				Init(str.data_);
			
			std::cout << "operator=" << std::endl;
		
		~String() 
			if (data_)  delete data_; 
			std::cout << "~String()" << std::endl;
		

	private:
		void Init(const char* str) 
			data_ = new char[len_ + 1];
			memcpy(data_, str, len_);
			data_[len_] = '\\0';
		
		char* data_ = nullptr;
		uint32_t len_ = 0;
;

int main() 
	String a;
	a = String("hello");
	std::vector<String> vec;
	vec.push_back("world");

	return 0;

输出:

String()
String(const char*)	// 1
operator=		// 2
~String()
String(const char*)	// 3
String(const String&)	// 4
~String()
~String()
~String()

上述代码中,String(“hello”) 和 String(“world”) 都是临时对象,也就是右值。整个过程中,我们实际只需要2个对象,即只需要2次内存分配即可,实际确是4次,造成了没有意义的资源申请和释放的操作。如果能够直接使用临时对象已经申请的资源,既能节省资源,有能节省资源申请和释放的时间。这正是定义转移语义的目的。


巧的是:C++11后 vector提供了 emplace_back() 可以就地构造对象,从而减少一次拷贝构造。不过对于 a = String("hello") 我们还得借助转移语义。


下面给我们为 String 添加移动构造函数及移动拷贝赋值函数。

#include <iostream>
#include <vector>
#include <string.h>

class String 
	public:
		String() 
			std::cout << "String()" << std::endl;
		;
		String(const char* str) 
			len_ = strlen(str);
			Init(str);
			std::cout << "String(const char*)" << std::endl;
		
		String(const String& str) 
			len_ = str.len_;
			Init(str.data_);
			std::cout << "String(const String&)" << std::endl;
		
		String(String&& str) 						// 移动构造函数
			len_ = str.len_;
			data_ = str.data_;
			str.len_ = 0;
			str.data_ = nullptr;
			std::cout << "move String(const String&&)" << std::endl;
		
		String& operator= (const String& str) 
			if (this != &str) 
				len_ = str.len_;
				delete data_;
				Init(str.data_);
			
			std::cout << "operator=" << std::endl;
		
		String& operator= (String&& str) 				// 移动赋值函数
			if (this != &str) 
				len_ = str.len_;
				delete data_;
				data_ = str.data_;
				str.len_ = 0;
				str.data_ = nullptr;
			
			std::cout << "move operator=" << std::endl;
		
		~String() 
			if (data_)  delete data_; 
			std::cout << "~String()" << std::endl;
		

	private:
		void Init(const char* str) 
			data_ = new char[len_ + 1];
			memcpy(data_, str, len_);
			data_[len_] = '\\0';
		
		char* data_ = nullptr;
		uint32_t len_ = 0;
;

int main() 
	String a;
	a = String("hello");
	std::vector<String> vec;
	vec.push_back("world");

	return 0;
输出如下:

String()
String(const char*)		// 1
move operatro=
~String()
String(const char*)		// 2
move String(const String&&)
~String()
~String()
~String()
原先的拷贝构造函数和拷贝赋值函数的调用被移动函数替代,减少了两次构造过程,节省了资源,提高了程序运行的效率。


几个注意点

1. 参数(右值)的符号必须是右值引用符号,即“&&”。
2. 参数(右值)不可以是常量,因为我们需要修改右值。
3. 参数(右值)的资源链接和标记必须修改。否则, 右值的析构函数就会释放资源。转移到新对象的资源也就无效了。




标准库函数 std::move

编译器只对右值引用才能调用转移构造函数和转移赋值函数,而所有命名对象都只能是左值引用,那么如何对左值使用移动函数,即把一个左值引用当做右值引用来使用,怎么做呢?标准库提供了函数 std::move,这个函数以非常简单的方式将左值引用转换为右值引用。

#include <iostream>

void fun(int& i) 
	std::cout << "lvalue:" << i << std::endl;


void fun(int&& i) 
	std::cout << "rvalue:" << i << std::endl;


int main() 
	int a = 0;
	fun(std::move(a));
	fun(1);
	return 0;
输出如下:

rvalue:0
rvalue:1

std::move在提高 swap 函数的的性能上非常有帮助,一般来说,swap函数的通用定义如下:

template <classT> swap(T& a, T& b) 

       T tmp(a);   // copy a to tmp 
       a = b;      // copy b to a 
       b = tmp;    // copy tmp to b 
有了 std::move,swap 函数的定义变为 :

template <classT> swap(T& a, T& b) 

       T tmp(std::move(a)); // move a to tmp 
       a = std::move(b);    // move b to a 
       b = std::move(tmp);  // move tmp to b 


通过 std::move,一个简单的 swap 函数就避免了 3 次不必要的拷贝操作。

虚幻4与现代c++:转移语义和右值引用(代码片段)

...4使用C++14标准开发,用到了很多现代C++的特性ÿ 查看详情

c++11新特性详解(代码片段)

C++11新特性详解C++11简介列表初始化{}初始化std::initializer_list声明auto(在C++中不支持C语言中原来auto的用法)decltypenullptr范围for循环STL中一些变化右值引用和移动语义左值引用和右值引用左值引... 查看详情

c++11新特性详解(代码片段)

C++11新特性详解C++11简介列表初始化{}初始化std::initializer_list声明auto(在C++中不支持C语言中原来auto的用法)decltypenullptr范围for循环STL中一些变化右值引用和移动语义左值引用和右值引用左值引... 查看详情

c++11新特性详解(代码片段)

C++11新特性详解C++11简介列表初始化{}初始化std::initializer_list声明auto(在C++中不支持C语言中原来auto的用法)decltypenullptr范围for循环STL中一些变化右值引用和移动语义左值引用和右值引用左值引... 查看详情

虚幻4与现代c++:转移语义和右值引用(代码片段)

...4使用C++14标准开发,用到了很多现代C++的特性,而且它不使用标准库,这可能就需要我们对现代C++理解的更清晰一点。于是,打算把自己对于虚幻引擎中的现代C++编程的理解整理成博客ÿ... 查看详情

c++11新特性:19——c++11右值引用(代码片段)

...础上,C++11标准对C++语言增添了约140个新特性。本节要讲的右值引用就是众多新特性中的一个,同时也是最重要的特性之一。很多初学者都感 查看详情

c++11特性(详细版)(代码片段)

...达式lambda表达式语法10、可变参数列表(先学会基本特性)可变参数包结合完美转发的好处11、包装器1、C11优势  相比C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C... 查看详情

如何评价c++11的右值引用(rvaluereference)特性?

参考技术A在C++中,有一个左值(lvalue)是一个数量可以得到它的地址。由于经常出现在赋值语句的左边,所以称为左值。传统的C++参考是左值引用。C++11,一个新的右值引用的概念。    首先我们来看看什么是所谓... 查看详情

c++11之右值引用:移动语义和完美转发(带你了解移动构造函数纯右值将亡值右值引用std::moveforward等新概念)(代码片段)

C++11新特性集合C++11之正则表达式(regex_match、regex_search、regex_replace)C++11之线程库(Thread、Mutex、atomic、lock_guard、同步)C++11之智能指针(unique_ptr、shared 查看详情

c++11新特性:20——c++11移动构造函数的功能和用法(代码片段)

原文地址:http://c.biancheng.net/view/vip_8694.html《C++11右值引用》一节中,给读者详细介绍了C++右值引用的含义和用法,同时还提到“右值引用主要用于实现移动(move)语义和完美转发”。有关完美转... 查看详情

c++11特性(详细版)(代码片段)

C111、C11优势2、列表初始化3、变量类型推导1、为什么需要类型推导2、decltype类型推导(了解)为什么需要decltypedecltype4、final与overridefinaloverride5、默认成员函数控制1、显示缺省函数2、删除默认函数(禁止调用)6... 查看详情

c++11的右值引用移动语义

...就会发现这个概念很简单,并无什么高深的地方。先说说右值引用。右值一般指的是表示式中的临时变量,在c++中临时变量在表达式结束后就被销毁了,之后程序就无法再引用这个变量了。但是c++11提供了一个方法,让我们可以... 查看详情

c++11新特性:22——c++11引用限定符的用法(代码片段)

原文地址:http://c.biancheng.net/view/8598.html在《C++右值引用》一节中,我们给您介绍了左值和右值。值得一提的是,左值和右值的区分也同样适用于类对象,本节中将左值的类对象称为左值对象,将右值的... 查看详情

c++11特性(代码片段)

C++11一、C++11简介二、列表初始化C++98中的初始化问题自定义类型的列表初始化三、变量类型推导decltype类型推导四、范围循环for五、final与override六、智能指针七、新增加容器--静态数组array、forward_list以及unordere... 查看详情

[转][c++11]我理解的右值引用移动语义和完美转发(代码片段)

c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能。有点难理解,于是花时间整理一下自己的理解。左值、右值C++中所有的值都必然属于左值、右值二者之一。左值是指表达式结束后依然存在的持久化对象,... 查看详情

c++11的新鲜事儿~(代码片段)

...erride新容器默认成员函数控制显式缺省函数删除默认函数右值引用右值引用概念左值与右值引用与右值引用比较值的形式返回对象的缺陷移动语义右 查看详情

c++11新特性:21——c++11move()函数:将左值强制转换为右值(代码片段)

原文地址:http://c.biancheng.net/view/7863.html通过学习《C++11移动构造函数》一节我们知道,C++11标准中借助右值引用可以为指定类添加移动构造函数,这样当使用该类的右值对象(可以理解为临时对象)... 查看详情

逐个使用c++11新特性(代码片段)

C++11auto&decltypeauto:根据变量初始值来推导变量类型,并用右值初始化变量。decltype:从表达式推导出类型,并将变量定义为该类型,但不用表达式的值初始化该变量。这里要注意下:decltype(i)--是i的类型,而decltype((i))就是引... 查看详情