9

试图了解使用变量是否std::forwardauto&&传递这些变量以允许移动的正确方法。

假设有一个函数:

void moveWidget(Widget&& w);

和调用者 - 两个变量来引用右值和左值:

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通用参考

在我的示例中,很明显uniRefRVis rvalueuniRefLVis lvalue但从概念上讲,它们都是通用引用,如果定义不同,它们可以表示rvaluelvalue

现在,我想调用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

您认为我们应该平等对待参考文献uniRefLVuniRefRV?我们应该使用三个选项中的哪一个来实现完美转发?

4

4 回答 4

11

你误解了指导方针。或者至少从字面上理解它。

我认为这里需要意识到三件重要的事情。

首先,所有类型在这里都是已知的。通用引用的建议主要适用于带有模板的通用代码,您根本不知道某些东西是左值引用还是右值引用。

其次,该函数采用右值引用:您必须传递一个右值。时期。这里没有选择。

合乎逻辑的结论是你不想传递一个通用引用:你传递的任何东西都必须是一个右值,它永远不可能是一个左值。通用引用可以是左值(如果它们被实例化为左值引用)。传递通用引用意味着“我不知道这是什么类型的引用,我可以将它作为右值或左值传递,所以我完全按照我得到的方式传递它”。这个问题的情况更像是“我确切地知道我必须通过什么,所以这就是我将要通过的”。

于 2013-07-02T15:40:11.597 回答
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);
于 2013-07-02T15:56:03.323 回答
4

我觉得你在这里有点搞混了。“通用参考”也不uniRefLV是。uniRefRV它们只是变量,并且都有明确的类型。这两种类型恰好都是引用类型,但这并不重要。

要调用moveWidget,您必须std::move在两种情况下都使用,并且在这两种情况下,语义都是在移动之后,您的原件w已被移动,因此不再处于确定状态。(你能做的最好的事情就是销毁它或重新分配给它。)

现在,相比之下,真正的通用引用模板参数,并且它必须始终采用规范形式:

template <typename T> void foo(T &&);
//                            ^^^^^^

foo(bar());
foo(x);
foo(std::move(y));

那是,

  • 模板是功能模板,
  • 模板参数T作为函数参数出现T &&,并且
  • 推导出实现的特化的模板参数。

当满足这些条件时,T &&就是通用引用,并且您将它传递给 via std::forward<T>,它保留了正确的引用类型。

于 2013-07-05T21:08:23.107 回答
0

(1).a 它不违反准则。你特别说“我不再需要那个左值所以接受它”这基本上就是 std::move 的用途。如果您在完成 std​​::move 后继续使用 uniRefLV - 这违反了指导方针。该值已消失(可能),您将其传递了过去。这就是斯科特所说的。

(1).b 这是合法的。虽然那里不需要 std::move ,但它确实简化了源代码的阅读。

(2), (3) 我们不必为 std::forward 提供模板参数。它应该由编译器推导出来!至少我的理解是这样的。而且我认为手动操作没有意义。转发只是从类型定义中删除引用(&& 和 &)。这就是为什么它被称为转发并用于数据转发。当你有函数时:template void foo(T && 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()它内部传递给的任何函数。

于 2013-07-10T08:34:00.070 回答