【C/C++】C++11新特性:初探右值引用与转移语义

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


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 次不必要的拷贝操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值