【问题标题】:Do I use std::forward or std::move here?我在这里使用 std::forward 还是 std::move ?
【发布时间】:2013-06-25 16:26:05
【问题描述】:

假设我有:

template<class T>
struct NodeBase
{
    T value;
    NodeBase(T &&value)
        : value(value) { }
};

我继承了它:

template<class T>
struct Node : public NodeBase<T>
{
    Node(T &&value)
        : NodeBase( WHAT_GOES_HERE (value)) { }
};

WHAT_GOES_HERE 应该是 std::move 还是 std::forward&lt;T&gt;?为什么?

【问题讨论】:

  • 我必须停下来想一想才能确定我脑海中的答案。
  • 哈哈!由于所有答案都已删除,我们discovered that the constructor is doing reference collapsing,您可以将左值引用传递给它。
  • 引用折叠很简单:typedef int&amp; T; T&amp;&amp; x;, tadah
  • @Mehrdad 你想要什么行为?你不能在那里放任何东西,这会导致 some 行为。 Node&lt;T&amp;&gt; 是什么意思,你希望从什么样的对象中构造它? Node&lt;T&amp;&amp;&gt;Node&lt;T&gt;Node&lt;T const&amp;&gt;Node&lt;T const&amp;&amp;&gt; 怎么样?
  • @MooingDuck 当然:通用引用不需要类型推断上下文。在类型推断上下文之外,它们只是相对无用!在类型推导/完美转发的上下文中,你想要的行为是比较明显的:在这里,它就不那么明显了。

标签: c++ c++11 move-semantics rvalue-reference


【解决方案1】:

由于在 Node&lt;T&gt; 的构造函数的实现中,不知道 T 是普通类型(即不是引用)还是引用,

std::forward<T>(value)

适合。

std::forward&lt;T&gt;(value) 在不知道T &amp;&amp; 是绑定到右值还是左值时是正确的选择。这里是这种情况,因为在构造函数中,我们不知道对于某些普通类型UT &amp;&amp; 是否等同于U &amp;&amp;,或者等同于U &amp; &amp;&amp;

T 是在使用 std::forward 的函数调用中推导出来还是在不同时间确定(例如在您的示例中,T 是在 Node模板已实例化)。

std::forward&lt;T&gt;(value) 将调用继承的构造函数,就像调用者直接调用基类构造函数一样。即,当value 是左值时,它将在左值上调用它,当value 是右值时,它将在右值上调用它。

【讨论】:

  • 但你是对的,std::forward&lt;T&gt; 似乎是最好的选择:coliru.stacked-crooked.com/…。 (所指出的错误是预期的,只是实验)
  • U &amp; &amp;&amp; U&amp; 因为引用折叠。
  • @MooingDuck 是的,当然。我把它写成U &amp; &amp;&amp; 是为了强调它由T = U &amp;(声明的类型)和&amp;&amp;(来自函数参数列表)组成。
【解决方案2】:

可能两者都没有。

我怀疑你应该拥有的是:

template<class T>
struct NodeBase
{
  T value;
  NodeBase(NodeBase &&) = default;
  NodeBase(NodeBase const&) = default; // issue: this might not work with a `T&`, but we can conditionally exclude it through some more fancy footwork
  NodeBase(NodeBase &) = default;
  template<typename U, typename=typename std:enable_if< std::is_convertible<U&&, T>::value >::type >
  NodeBase(U &&u)
    : value(std::forward<U>(u)) { }
};

template<class T>
struct Node : public NodeBase<T>
{
  Node( Node & ) = default;
  Node( Node const& ) = default; // issue: this might not work with a `T&`, but we can conditionally exclude it through some more fancy footwork
  Node( Node && ) = default;
  template<typename U, typename=typename std:enable_if< std::is_convertible<U&&, NodeBase<T>>::value >::type>
  Node(U && u)
    : NodeBase( std::forward<U>(u) ) { }
};

除非你正在做一些非常奇怪的事情。

非常奇怪,这意味着如果您的Tint,您只想接受移动值到您的Node,但如果您的Tint&amp;,您接受只有非常量左值ints,如果Tint const&amp;,则接受任何可转换为int 的值。

这对NodeBase 的构造函数提出了一组奇怪的要求。我能想到可能会出现这种情况的情况,但并不常见。

假设您只是希望NodeBase 存储该数据,在构造函数中使用T&amp;&amp; 是不正确的做法——如果您在NodeBase 中存储int,您可能愿意int 的副本,而不是只接受从 ints 移出的。

上面的代码正是这样做的——它允许任何可以存储在NodeBase中的东西都可以通过完美的转发传递到NodeBase

另一方面,如果您真的想要一组奇怪的构造限制,那么这不是您的正确答案。我在构建从通用引用参数构建的template 类型时使用过它,并且我确实想限制传入的类型以完全匹配通用引用参数,并在参数是右值引用,否则保留对它的引用。

【讨论】:

  • 这可能是也可能不是问题所寻求的答案,但对于处于类似但更标准情况的任何人来说,这是一个很好的答案。 +1。
  • +1 这让我仔细检查了我写的内容,你很可能是对的!
【解决方案3】:

T 在您的示例中没有推断出来。 T 是类模板参数,而不是函数模板参数。因此,假设您不会将引用类型用作 T,T&amp;&amp; 将是对 T 的右值引用,因此它只会绑定到 T 类型的右值。这些将是安全的,因此您可以在此处使用std::move

template<class T>
struct Node : public NodeBase<T>
{
    Node(T &&value)
        : NodeBase( std::move (value)) { }
};

int main()
{
    int i = 3;

    Node<int> x(42); // ok
    Node<int> y(std::move(i)); // ok
    Node<int> z(i); // error
}

std::forward 通常仅适用于推导类型可能是左值引用或右值引用的地方。

template<class T>
void f(T&& x)
{
    ... std::forward<T>(x) ...
}

因为T&amp;&amp; 实际上可能是左值引用或右值引用。这只是因为T是在这个上下文中推导出来的。

【讨论】:

  • 当我在 Visual C++ 中尝试 template&lt;class T&gt; struct MyStruct { MyStruct(T &amp;&amp;) { } }; int main() { int x = 5; MyStruct&lt;int &amp;&gt; m(x); } 时,它可以很好地折叠引用。编译器错了,还是这个答案? :P
  • @Mehrdad:没错,通常你不会使用引用类型作为模板参数。如果这是你的类的用法,那么你应该使用 std::forward 来保留引用类别。
  • 如果他使用std::forward,那么它会因非引用类型而中断。 @Mehrdad:经验法则:不要在模板参数中引用引用,除非这是类的唯一目的
  • 我已经忘记了我们在这里想要做什么。在 OP 代码中,我们存储了一个 T 类型的值,并将引用作为构造函数参数传递。您希望 T 的不同类型参数的行为是什么?
  • @MooingDuck 你说如果使用std::forward&lt;T&gt;,它会因非引用类型而中断。你能解释一下吗?到底是什么坏了?
猜你喜欢
  • 2016-07-08
  • 2015-05-03
  • 2012-10-24
  • 2023-02-09
  • 2016-03-21
  • 1970-01-01
  • 2016-02-22
  • 2018-07-05
  • 1970-01-01
相关资源
最近更新 更多