翻译「C++ Rvalue References Explained」C++右值引用详解 Part7:Perfect Forwarding(完美转发):问题
本文为第七部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/cpp-rvalue-references-explained-introduction.html。
Perfect Forwarding(完美转发):问题
Move语义背后右值引用用来解决的另一个问题是完美转发问题。考虑下面这样简单的工厂函数:
template<typename T, typename Arg> shared_ptr<T> factory(Arg arg) { return shared_ptr<T>(new T(arg)); }
显然,这里的目的是想要将arg参数通过工厂函数传递到T的构造函数中去。理想情况,就arg而言,一切应该表现得如同没有工厂函数一样,构造器直接被客户的代码调用:也就是完美转发。上面的代码很不幸在这种状况下失败了:它引入了一个额外的传值调用,当构造器使用引用类型作为参数时,那么这样就是很糟糕的了。
一个更为普遍的解决方案,例如被boost::bind所采用的,就是让外层的函数的参数使用引用类型:
template<typename T, typename Arg> shared_ptr<T> factory(Arg& arg) { return shared_ptr<T>(new T(arg)); }
这样更好,但也并非完美。问题就是现在的工厂函数并不能被右值所调用:
factory<X>(hoo()); // error:如果hoo通过值返回 factory<X>(41); // error
这可以通过提供一个const reference的参数重载来解决:
template<typename T, typename Arg> shared_ptr<T> factory(Arg const & arg) { return shared_ptr<T>(new T(arg)); }
这个方法有两个问题。首先,如果factor不仅仅只有一个,而是有很多个参数的话,你就需要提供所有对non-const和const reference参数的组合重载。因此,这种解决方案当面临到函数有很多参数的时候可伸缩性是很差的。
第二个,这种方法并不是完美的是因为它会阻挡move语义:在factory函数体中的T构造器重的参数是一个左值。因此,move语义永远不会发生即使没有外在的包装函数。
事实证明右值引用可以用来解决这类问题。它使得可以不用通过任何的重载就可以实现真正的完美转发。为了理解它是如果运作的,我们需要先看一下右值引用的两个规则。