尝试了解使用带有std::forward
变量的auto&&
是否是传递这些变量以允许移动的正确方法。
假设有一个功能:
void moveWidget(Widget&& w);
调用者 - 两个引用rvalue和lvalue的变量:
Widget w;
auto&& uniRefLV = w; // lvalue initialiser,
// uniRefLV's type is Widget&
auto&& uniRefRV = std::move(w); // rvalue initialiser,
// uniRefRV's type is Widget&&
我们知道auto&&
类型的变量是通用引用,因为存在类型推断。这意味着uniRefRV
和uniRefLV
都是通用引用。
在我的示例中,很明显uniRefRV
是 rvalue 而uniRefLV
是 lvalue ,但从概念上讲,它们都是通用引用< / em>如果定义不同,则可以表示 rvalue 或 lvalue 。
现在,我想调用moveWidget()
并完善这些通用引用类型。该指南(由Scott Meyers撰写)说:
通过
std::move
,通用参考通过std::forward
传递和返回右值参考。
除非我完全误解了指南,否则使用std::forward
似乎合乎逻辑。但是,让我们考虑所有可能的选择:
// (1) std::move:
moveWidget(std::move(uniRefLV)); // Compiles and looks fine
// but violates the guideline?
// (unconditionally casts lvalue to rvalue)
moveWidget(std::move(uniRefRV)); // Same as above - but not an issue here
// as we cast rvalue to rvalue
// (2) std::forward with Widget:
moveWidget(std::forward<Widget>(uniRefLV)); // Compiles, follows the guideline
// but doesn't look right - what if
// we didn't know Widget's type?
moveWidget(std::forward<Widget>(uniRefRV)); // Same as above
// (3) std::forward with decltype:
moveWidget(std::forward<decltype(uniRefLV)>(uniRefLV)); // Fails to compile! (VC10)
// follows the guideline
// has nice and short syntax :)
moveWidget(std::forward<decltype(uniRefRV)>(uniRefRV)); // Compiles fine
您认为我们应该同等对待引用uniRefLV
和uniRefRV
以及我们应该使用哪三个选项进行完美转发?
答案 0 :(得分:11)
您误解了指南。或者至少从字面上看它。
我认为这里有三个重要的事情需要实现。
首先,这里所有类型都是已知的。对于通用引用的建议主要适用于带有模板的通用代码,如果某些内容是或者采用左值引用或右值引用,则根本不知道。
其次,该函数采用右值引用:您必须传递右值。期。这里没有选择。
逻辑上的结论是你不想要传递一个通用引用:无论你传递的是一个右值,它都不能是一个左值。通用引用可以是左值(如果它们被实例化为左值引用)。传递一个通用引用意味着“我不知道这是什么样的引用,我可以将它作为rvalue或左值传递,所以我正在传递它,就像我得到它一样”。这个问题的情况更像是“我确切地知道我必须通过什么,所以这就是我要传递的东西”。
答案 1 :(得分:5)
让我们假设案件并不像看起来那么简单。而不是giveMeInt
我们有类似的东西:
template <typename T>
typename complex_computation<T>::type giveMeSomething(T t);
而不是moveMe
,你有一些实际上需要通用引用的东西,因此不需要无条件的std::move
。
template <typename T>
void wantForwarded(T&&);
现在你实际上需要完美转发。
auto&& uniRef = giveMeSomething(iDontKnowTheTypeOfThis);
wantForwarded(std::forward<decltype(uniRef)>(uniRef);
答案 2 :(得分:4)
我觉得你有点混淆了。 uniRefLV
和uniRefRV
都不是“通用引用”。它们只是变量,两者都有一定的类型。这两种类型恰好都是引用类型,但这并不重要。
要调用moveWidget
,您必须在两种情况下使用std::move
,并且在这两种情况下,语义都是在移动后,您的原始w
已被移除,因此不再是一个明确的状态。 (你可以用它做的最好的事情是破坏它或重新分配它。)
现在,相比之下,真正的通用引用是模板参数,它必须始终是规范形式:
template <typename T> void foo(T &&);
// ^^^^^^
foo(bar());
foo(x);
foo(std::move(y));
即,
T
作为函数参数T &&
和满足这些条件时,T &&
是通用引用,您可以通过std::forward<T>
传递它,这会保留正确的引用类型。
答案 3 :(得分:0)
<强>(1).A 强> 它没有违反指南。你明确地说“我不再需要那个左值,所以接受它”这基本上就是std :: move的用途。如果你在完成std :: move之后继续使用uniRefLV - 这是违反准则的。价值消失了(可能),你把它传了过来。这就是 Scott 所说的。
<强>(1).B 强> 这是合法的。虽然那里不需要std :: move,但它确实简化了源代码的阅读。
(2),(3) 我们不必为std :: forward提供模板参数。它应由编译器推导出来!至少这就是我的理解。而且我没有看到手动完成这一点。转发只是从类型定义中删除引用(&amp;&amp;和&amp;)。这就是为什么它被称为转发并用于数据转发。当你有功能: 模板 void foo(T&amp; t);
可以将其实例化为:
void foo(SomeType & t);
void foo(SomeType && t);
void foo(const SomeType & t);
void foo(const SomeType && t);
void foo(volatile SomeType & t);
void foo(volatile SomeType && t);
void foo(const volatile SomeType & t);
void foo(const volatile SomeType && t);
例如:
Widget a;
foo(a);
将实例化:
void foo(Widget & a);
不
void foo(Widget a);
所以这些引用都被std :: forward删除了。只有*modificator* SomeType t
被呈现给传递给它的foo()
内的任何函数。