方法是否应该将R值引用作为输入?

时间:2016-12-02 17:08:24

标签: c++ c++11

正如标题中所表达的那样,我想知道是否存在给定类方法的有效原因/示例,不包括移动构造函数移动赋值运算符,或者自由函数应该作为输入参数,采用R值参考。

3 个答案:

答案 0 :(得分:8)

保留值时避免不必要的副本。我喜欢将此功能称为“sink functions”。定义 setters 时会经常发生这种情况。

class A
{
private:
    std::string _s;

public:
    void setS(const std::string& s) { _s = s; }
    void setS(std::string&& s) { _s = std::move(s); }
};

int main()
{
    A a;
    std::string s{"some long string ......."};

    a.setS(s); // copies and retains `s`
    a.setS(std::move(s)); // moves and retains `s`
}

答案 1 :(得分:7)

有两种方法可以建模接收器参数。

第一个是按价值计算:

void foo(std::string);

第二个是右值参考:

void foo(std::string&&);

包含const&重载的可能变体,以简化调用者的工作。

inline void foo(std::string const& s){
  auto tmp = s;
  return foo(std::move(tmp));
}

按照std::move加上&&const&,或者需要调用者手动复制非临时值并自己移动)。它不需要第二次过载。

因此,如果这一举动值得考虑,可以通过const&&&获取接收器参数可以为您节省一些动力。另外,如果副本费用太高,你可以在通话现场尴尬,从而阻止它。

但这不是唯一的原因。有时你想要检测某些东西是否是左值或右值,只有当它是右值时才会复制。

例如,假设我们有一个范围适配器backwardsbackwards采用适当的范围(你可以for(:)结束,并且可以反转其迭代器)并返回一个向后迭代它的范围。

天真地说,您所要做的就是从源范围获取beginend,然后制作反向迭代器并存储它们并从您自己的begin和{{1}返回它们方法。

可悲的是,这打破了:

end

因为从std::vector<int> get_some_ints(); for( int x : backwards( get_some_ints() ) ) { std::cout << x << "\n"; } 返回的临时值的生命周期没有被get_some_ints循环扩展!

for(:)大致扩展为:

for(:)

(上面有一些小谎言告诉上面的孩子,但这个讨论非常接近)。

特别是这一行:

{
  auto&& __range_expression = backwards( get_some_ints() );
  auto __it = std::begin( __range_expression );
  auto __end = std::end( __range_expression );
  for (; __it != __end; ++__it) {
    int x = *__it;
    std::cout << x << "\n";
  }
}

auto&& __range_expression = backwards( get_some_ints() ); 的返回值是终身延长的;但其论点的生命周期不是!

因此,如果backwards采用backwards,则R const&在循环之前被静默销毁,并且所涉及的迭代器无效。

因此vector 必须存储backwards 的副本才能使上述代码生效。这是我们让矢量持续足够长的唯一机会!

另一方面,在更传统的情况下:

vector

存储auto some_ints = get_some_ints(); for( int x : backwards( some_ints ) ) { std::cout << x << "\n"; } 的额外副本将是一个可怕的想法,并且非常意外。

所以在这种情况下,some_ints需要检测它的参数是rvalue还是左值,如果它是rvalue,它需要复制它并将它存储在返回值中,如果它是左值它只需要存储迭代器或对它的引用。

答案 2 :(得分:1)

有时你想拥有像std::vector那样大的东西,但是你想避免意外复制。

通过仅提供r值引用过载,想要传递副本的调用者必须明确地这样做:

class DataHolder {
  std::vector<double> a;
  std::vector<int> b;
public:
  DataHolder(std::vector<double>&& a, std::vector<int>&& b) : a(a), b(b) {}
};

auto a1 = makeLotsDoubles();
auto b1 = makeLotsInts();
DataHolder holder(std::move(a1), std::move(b1));  // No copies. Good.

auto a2 = makeLotsDoubles();
auto b2 = makeLotsInts();
DataHolder holder(a2, b2) // Forgot to move, compiler error.

如果您使用了pass-by-value,那么如果忘记在左值上使用std::move,则会复制。