14

建议std::forward通常仅限于完美转发函数模板参数的规范用例;一些评论员甚至说std::forward. 但考虑这样的代码:

// Temporarily holds a value of type T, which may be a reference or ordinary
// copyable/movable value.
template <typename T>
class ValueHolder {
 public:
  ValueHolder(T value)
    : value_(std::forward<T>(value)) {
  }


  T Release() {
    T result = std::forward<T>(value_);
    return result;
  }

 private:
  ~ValueHolder() {}

  T value_;
};

在这种情况下,不会出现完美转发的问题:因为这是一个类模板而不是函数模板,客户端代码必须明确指定T,并且可以选择是否以及如何对其进行 ref 限定。同样,参数 tostd::forward不是“通用参考”。

尽管如此,std::forward这里似乎很合适:我们不能把它排除在外,因为当它T是只移动类型时它不起作用,我们不能使用它,因为它在是左值引用类型std::move时不起作用T. 当然,我们可以部分专门ValueHolder针对引用和std::move值使用直接初始化,但这在std::forward完成这项工作时似乎过于复杂。这似乎也是一个合理的概念匹配的含义std::forward:我们试图一般地转发可能是也可能不是引用的东西,只是我们将它转​​发给函数的调用者,而不是我们调用的函数我们自己。

这是合理的使用std::forward吗?有什么理由避免它吗?如果是这样,首选的选择是什么?

4

2 回答 2

9

std::forward是一个条件move转换(或更技术上的右值转换),仅此而已。虽然在完美的转发上下文之外使用它是令人困惑的,但如果move适用相同的条件,它可以做正确的事情。

我很想用一个不同的名字,即使只有一个薄的包装forward,但我想不出一个适合上述情况的好名字。

或者使条件移动更明确。IE,

template<bool do_move, typename T>
struct helper {
  auto operator()(T&& t) const
  -> decltype(std::move(t))
  {
      return (std::move(t));
  }
};
template<typename T>
struct helper<false, T> {
  T& operator()(T&& t) const { return t; }
};
template<bool do_move, typename T>
auto conditional_move(T&& t)
 ->decltype( helper<do_move,T>()(std::forward<T>(t)) )
{
    return ( helper<do_move,T>()(std::forward<T>(t)) );
}

bool如果为假,move如果为真,那是一个 noop 传递。然后,您可以使您的等价物std::forward更加明确,并且更少依赖用户理解 C++11 的奥秘来理解您的代码。

利用:

std::unique_ptr<int> foo;
std::unique_ptr<int> bar = conditional_move<true>(foo); // compiles
std::unique_ptr<int> baz = conditional_move<false>(foo); // does not compile

另一方面,你在上面做的那种事情需要对右值和左值语义有相当深入的理解,所以也许forward是无害的。

于 2013-12-16T17:57:23.420 回答
1

只要使用得当,这种包装器就可以工作。std::bind做类似的事情。但是这个类也反映了当 getter 执行移动时它是一次性的功能。

ValueHolder用词不当,因为它也明确支持引用,这与值相反。采用的方法std::bind是忽略左值性并应用值语义。为了获得参考,用户申请std::ref. 因此,引用由统一的值语义接口建模。我也使用了一个自定义的一次性rref课程bind

在给定的实现中有几个不一致的地方。将return result;在右值引用特化中失败,因为名称result是左值。你需要另一个forward。同样,一个const限定版本的Release,它会删除自身并返回存储值的副本,将支持 const 限定但动态分配的对象的偶尔极端情况。delete(是的,你可以const *。)

此外,请注意裸引用成员会使类不可分配。std::reference_wrapper也可以解决这个问题。

至于使用forward本身,它遵循其宣传的目的,只要它根据为原始源对象推导出的类型传递一些参数引用。大概在未显示的类别中,这需要额外的步法。用户不应该写一个显式的模板参数......但最后,只要没有错误,什么是好的,仅仅是可接受的或hackish是主观的。

于 2013-12-17T06:11:29.697 回答