右值引用、移动语义、完美转发

右值引用、移动语义、完美转发

右值引用(rvalue reference)是 C++11 为了实现移动语意(move semantic)和完美转发(perfect forwarding)而提出来的。
右值引用,简单说就是绑定在右值上的引用右值的内容可以直接移动(move)给左值对象,而不需要进行开销较大的深拷
贝(deep copy)

移动语义

下面这个例子:

v2 = v1 调用的是拷贝赋值操作符,v2 复制了 v1 的内容 —— 复制语义
v3 = std::move(v1) 调用的是移动赋值操作符,将 v1 的内容移动给 v3 —— 移动语义
使用std::move只会将它们转换为右值引用类型。在使用std::move时需要谨慎,确保目标类型支持移动语义。在这里插入图片描述
为了实现移动语意,C++ 增加了与拷贝构造函数(copy constructor)和拷贝赋值操作符(copy assignment operator)对应的移动构造函数(move constructor)和移动赋值操作符(move assignment operator),通过函数重载机制来确定应该调用拷贝语意还是移动语意(参数是左值引用就调用拷贝语意;参数是右值引用就调用移动语意)。 再来看一个简单的例子:

#include <iostream> 
#include <vector>
#include <string>

class foo
{
public:
	foo() {
		std::cout << "Default Constructor:" << Info() << std::endl;
	}
	foo(const std::string& s, const std::vector<int>& v) :s_(s), v_(v) {
		std::cout << "User-Defined Constructor:" << Info() << std::endl;
	}
	~foo() { std::cout << "Destructor: " << Info() << std::endl; }
	//copy
	foo(const foo& f) :s_(f.s_), v_(f.v_) {
		std::cout << "copy Constructor: " << Info() << std::endl;
	}
	//Copy Assignment:
	foo& operator= (const foo& f) {
		s_ = f.s_;
		v_ = f.v_;
		std::cout << "Copy Assignment:" << Info() << std::endl;
		return *this;
	}
	//move,右值引用不能加const
	foo(foo&& f) :s_(std::move(f.s_)), v_(std::move(f.v_)) {
		std::cout << "Move Constructor: " << Info() << std::endl;
	}
	//move operator
	foo& operator=(foo&& f) {
		s_ = std::move(f.s_);
		v_ = std::move(f.v_);
		std::cout << "Move Assignment: " << Info() << std::endl;
		return *this;
	}
	std::string Info() {
		return "{" + (s_.empty() ? "'empty'" : s_) + ", " +
			std::to_string(v_.size()) + "}";
	}
private:
	std::string s_;
	std::vector<int> v_;
};

int main() {
	std::vector<int> v(1024);

	std::cout << "================ Copy =======================" << std::endl;
	foo cf1("hello", v);
	foo cf2(cf1);  // 调用拷贝构造函数
	foo cf3;
	cf3 = cf2;  // 调用拷贝赋值操作符

	std::cout << "================ Move =========================" << std::endl;
	foo f1("hello", v);
	foo f2(std::move(f1));  // 调用移动构造函数
	foo f3;
	f3 = std::move(f2);  // 调用移动赋值操作符

	const foo f4("what",v);
	foo f5(std::move(f4));
	return 0;
}

在这里插入图片描述
简单封装了一个类 Foo,重点是实现:
拷贝语意:拷贝构造函数 Foo(const Foo&) 、拷贝赋值操作符 Foo& operator=(const Foo&) 。
移动语意:移动构造函数 Foo(Foo&&) 、移动赋值操作符 Foo& operator=(Foo&&) 。
拷贝语意相信大部分人都比较熟悉了,也比较好理解。在这个例子中,每次都会拷贝 s_ 和 v_ 两个成员,最后 cf1、cf2、cf3 三个对象的内容都是一样的。 每次执行移动语意,是分别调用 s_ 和 v_ 的移动语意函数——理论上只需要对内部指针进行修改,所以效率较高。执行移动语意的代码片段了出现了一个标准库中的函数 std::move —— 它可以将参数强制转换成一个右值。本质上是告诉编译器,我想要 move 这个参数——最终能不能 move 是另一回事——可能对应的类型没有实现移动语意,可能参数是 const 的。 有一些场景可能拿到的值直接就是右值,不需要通过 std::move 强制转换

在这里插入图片描述在这里插入图片描述

完美转发

C++ 通过了一个叫 std::forward 的函数模板来实现完美转发。这里直接使用 Effective Modern C++ 中的例子作为说明。在前面的例子上,我们增加如下的代码:

// 接受一个 const 左值引用
	void Process(const foo& f) {
		std::cout << "lvalue reference" << std::endl;
		// ...
	}
	// 接受一个右值引用
	void Process(foo&& f) {
		std::cout << "rvalue reference" << std::endl;
		// ...
	}
	template <typename T>
	void LogAndProcessNotForward(T&& a) {
		std::cout << a.Info() << std::endl;
		Process(a);
	}
	template <typename T>
	void LogAndProcessWithForward(T&& a) {
		std::cout << a.Info() << std::endl;
		Process(std::forward<T>(a));
	}

在这里插入图片描述
左值?右值?
到底什么时候是左值?什么时候是右值?是不是有点混乱? 在 C++ 中,每个表达式(expression)都有两个特性:
1、has identity? —— 是否有唯一标识,比如地址、指针。有唯一标识的表达式在 C++ 中被称为 glvalue(generalized lvalue)。(广义左值)
2、can be moved from? —— 是否可以安全地移动(编译器)。可以安全地移动的表达式在 C++ 中被成为 rvalue
根据这两个特性,可以将表达式分成 4 类:
has identity and cannot be moved from - 这类表达式在 C++ 中被称为 lvalue。(左值)
has identity and can be moved from - 这类表达式在 C++ 中被成为 xvalue(expiring value)。(将亡值)
does not have identity and can be moved from - 这类表达式在 C++ 中被成为 prvalue(pure rvalue)。(纯右值)
does not have identity and cannot be moved -C++ 中不存在这类表达式。
在这里插入图片描述
完美转发必须通过模板才能实现,因为模板在编译时期具有类型信息,可以精确地控制值的传递方式,从而实现完美转发。具体来说,模板可以识别出参数的类型,并自动将参数以正确的方式传递给其他函数。例如,如果参数是一个左值,模板可以将它传递给需要左值参数的函数;如果参数是一个右值,模板可以将它传递给需要右值参数的函数。这样,模板可以在调用其他函数时保持参数的原有语义不变,从而实现完美转发。因此,模板是实现完美转发的重要工具

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值