通过“&&”传递的参数对非构造函数有用吗?
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
的自由存储缓冲区交换,并且 s
将 delete[]
作为 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)
来执行移动。除了降低有人意外复制的可能性外,它还记录了我们正在取得数据的所有权。你也不一定需要移动整个物体,你可能只是偷了物体的一部分。
一个人可能有一个函数 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
的自由存储缓冲区交换,并且 s
将 delete[]
作为 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)
来执行移动。除了降低有人意外复制的可能性外,它还记录了我们正在取得数据的所有权。你也不一定需要移动整个物体,你可能只是偷了物体的一部分。