通过“&&”传递的参数对非构造函数有用吗?

Are arguments passed via `&&` useful for non constructor functions?

一个人可能有一个函数 void setData(std::string arg); 并通过 setData(std::move(data)); 调用它从而调用移动构造函数,他会对 void setData(std::string && arg); 做同样的事情(除了他会被迫移动数据进去)。编译器不能决定是否对简单的情况执行移动,是否已经有人这样做了?

所以我的问题是:不仅要为编译器而且要为一般代码(例如为其他开发人员创建的 API 成员)使用 &&

Cant compiler decide do if use of move shall be performed for simple cases, does any do it already?

即使只有 void setData(std::string arg);,编译器也会像临时变量一样自动移动值:

x.setData(my_string + "more");  // move from temporary std::string
x.setData(get_a_string()); // move returned-by-value string

至于&&-重载的效用,请考虑一些调用代码:

std::string q = get_a_string();
myObj.setData(std::move(q));
q = pick_a_string_to_copy[rand() % 10];

在这里,如果只有 setData(std::string s) 重载,那么 s 的移动构造函数将取得 q 的所有权,任何有效的实现都会离开 q没有任何指向动态分配的自由存储的指针。然后 setData() 可能会分配给数据成员,它将与 s 的自由存储缓冲区交换,并且 sdelete[] 作为 setData returns。上面的下一个 q = 行需要为其下一个值分配一个新缓冲区(假设 size() 大于任何短字符串优化缓冲区)。

这与 setData(std::string&& s) 过载的情况形成对比,最终 q 的缓冲区可能会与 myobj 的数据成员的缓冲区交换,这样 q 仍然拥有动态内存,可能刚好足以存储下一个分配给它的值——节省一点时间。另一方面,缓冲区可能比需要的大,占用内存的时间比需要的长。

简单地说,使用 && 调用者可以交易自由存储缓冲区,而不是丢失缓冲区或进行低效复制。

总而言之,功能差异通常并不相关,但它们确实存在。

优化 r 值

比较 void setData(std::string arg)void setData(std::string&& arg)。在第一种情况下,我假设 setData 将数据移动到位

class Widget {
  std::string data;
 public:
  void setData(std::string data) { this->data = std::move(data); }
};

如果我们这样称呼它

w.setData(std::move(data));

我们将调用一次移动构造函数来构造函数参数,并调用一次移动赋值运算符将数据移动到成员变量中。所以一共走两步。

但是如果我们像这样重载右值引用:

class Widget {
  std::string data;
 public:
  void setData(std::string&& data) { this->data = std::move(data); }
};

您只会调用一次移动赋值运算符。你可能会说 "But moves are cheap!" 这可能是真的。在某些情况下,它们并不便宜(例如 std::array),而在 std::string 的情况下,大多数编译器实施小字符串优化 (SSO),因此对于小字符串,移动并不比复制便宜。

优化左值

按值传递的论点通常是您可以优化左值和右值,而无需提供两个重载 void setData(const std::string& arg)void setData(std::string&& arg)。因此,让我们比较一下如果将左值传递给 void setData(std::string arg)void setData(const std::string& arg) 会发生什么。在第一种情况下,您将获得一份无条件副本,然后获得一份移动任务。在第二种情况下,你只得到一个任务。额外的移动分配可能微不足道,但无条件复制可能比分配昂贵得多。如果您对同一个对象多次调用 setData ,分配可能能够重新使用现有容量并避免重新分配。无条件复制总是必须进行分配。

这些注意事项在实践中可能微不足道,但值得了解。

Documenting/Enforcing所有权转让

r 值引用参数的另一个用途是 document/enforce 所有权转移。假设您有一些可复制和可移动的大对象,那么您可能只提供 void setData(BigData&& data) 来执行移动。除了降低有人意外复制的可能性外,它还记录了我们正在取得数据的所有权。你也不一定需要移动整个物体,你可能只是偷了物体的一部分。