0
点赞
收藏
分享

微信扫一扫

《Effective Modern C++》学习笔记 - Item 23: 理解 std::move 和 std::forward

微笑沉默 2022-01-28 阅读 47
  • std::move 不会移动任何东西,std::forward 不会传递任何东西。二者在运行时什么都不会做
  • std::movestd::forward 只是执行类型转换(cast)的函数(模板)std::move 无条件地将参数转换为右值,而 std::forward 只有当满足某个条件时才做如此转换。就这样。
  • 看看 std::move 的实现:
// GCC版本,MSVC与之区别仅在于将 remove_reference<_Tp>::type换成了remove_reference_t<_Ty>
template<typename _Tp>
constexpr typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t) noexcept
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
  • std::move 接受一个对象的万能引用参数(见Item 24),并返回该对象的 && 引用。如Item 28所解释,如果 T 是一个左值引用,那么 T&& 也会是左值引用。为了避免这种情况,先对 T 使用 std::remove_reference,这样能保证返回的是右值引用。也有提议认为可以将其命名为 rvalue_cast

  • 下面看一个例子。假设有一个类 Annotation,其中有一个成员 std::string value,在构造时由一个传入的参数 text 构造。由于构造函数只需要读取而不用修改 text 的值,你自然地考虑使用 const 修饰符:

class Annotation {
public:
	explicit Annotation(const std::string text) // 为何以值传递,见Item 41};

又考虑到想避免复制 text 的开销,你使用了 std::move 来将 text 转换为右值以构造 value

class Annotation {
public:
	explicit Annotation(const std::string text)
	: value(std::move(text)) 	// "move" text into value; this code
	{} 						// doesn't do what it seems to!private:
	std::string value;
};

以上代码可以通过编译、链接,也能正常运行,将 text 的值传给 value,但其方法是拷贝而非移动!原因在于,text 被声明为一个 const std::string,经 move 变成右值后,const 性质并不会消失const 的右值引用参数无法用于移动构造函数,编译器只能退而求其次选择拷贝构造。

  • 从这个例子中我们应该学到两件事:(1)不要将想要移动的对象声明为 const,否则后续的移动操作会静默地变成拷贝操作(即使用了 std::move 转型也是无效的)。(2)std::move 不仅不移动对象,甚至不保证它转型的对象是能够被移动的。你只能确定经 move 后的对象是一个右值。

  • std::forwardstd::move 类似,但它是一个带条件的类型转换。为了理解这个条件,先来看一个常见的使用场景:在模板函数中接受一个万能引用参数并将其传递给另一个函数。
void process(const Widget& lvalArg); 	// process lvalues
void process(Widget&& rvalArg); 		// process rvalues

template<typename T> 					// template that passes
void logAndProcess(T&& param) 			// param to process
{
	auto now = 							// get current time
		std::chrono::system_clock::now();
	makeLogEntry("Calling 'process'", now);
	process(std::forward<T>(param));
}

Widget w;
logAndProcess(w); 				// call with lvalue
logAndProcess(std::move(w)); 	// call with rvalue

在两次调用中,我们希望前者在 logAndProcess 中调用 process 的左值版本,而后者调用右值重载版本。param 是一个既能绑定到左值又能绑定到右值参数的万能引用,然而,param 自身是左值,直接将其传给 process 将永远调用左值版本。

我们希望有一种机制,当 param 是用左值的入参初始化时,调用左值版本;用右值的入参初始化时,将其转型为右值并调用右值版本。这正是 std::forward 所做的:只有当其参数是由右值初始化时,才将其转型为一个右值。其实现的诀窍在它的模板类型 T 中,详细原理见Item 28讲解。

  • 功能上说,std::movestd::forward 是可以互相替代的;说到底,它们只是一个类型转换,我们完全可以随便自己写。但还是有必要区分它们的使用:语法上,后者需要额外传递一个模板类型参数;语义上,二者代表了非常不同的动作:前者是执行一个向右值的转换,而后者只是将一个对象原样地传递(forward)给另一个函数,保留其左右值性。

总结

  1. std::move 进行向右值的无条件类型转换。其自身不会移动任何东西。(真正的移动是在使用 move 后的右值的函数中发生的)
  2. std::forward 只有当其参数是绑定到右值上时才将其类型转换为右值。(通常用于万能引用)
  3. std::movestd::forward 在运行时都不会做任何事。
举报

相关推荐

0 条评论