正如标题中所表达的那样,我想知道是否存在给定类方法的有效原因/示例,不包括移动构造函数和移动赋值运算符,或者自由函数应该作为输入参数,采用R值参考。
答案 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&
和&&
获取接收器参数可以为您节省一些动力。另外,如果副本费用太高,你可以在通话现场尴尬,从而阻止它。
但这不是唯一的原因。有时你想要检测某些东西是否是左值或右值,只有当它是右值时才会复制。
例如,假设我们有一个范围适配器backwards
。 backwards
采用适当的范围(你可以for(:)
结束,并且可以反转其迭代器)并返回一个向后迭代它的范围。
天真地说,您所要做的就是从源范围获取begin
和end
,然后制作反向迭代器并存储它们并从您自己的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
,则会复制。