C++ 正确使用右值引用作为参数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15673889/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Correct usage of rvalue references as parameters
提问by Grapes
Let's take the following method as an example:
我们以下面的方法为例:
void Asset::Load( const std::string& path )
{
// complicated method....
}
General use of this method would be as follows:
这种方法的一般用途如下:
Asset exampleAsset;
exampleAsset.Load("image0.png");
Since we know most of the time the Path is a temporary rvalue, does it make sense to add an Rvalue version of this method? And if so, is this a correct implementation;
既然我们知道大多数时候 Path 是一个临时的右值,那么添加这个方法的右值版本有意义吗?如果是这样,这是一个正确的实现吗?
void Asset::Load( const std::string& path )
{
// complicated method....
}
void Asset::Load( std::string&& path )
{
Load(path); // call the above method
}
Is this a correct approach to writing rvalue versions of methods?
这是编写方法的右值版本的正确方法吗?
回答by Cassio Neri
For your particular case, the second overload is useless.
对于您的特定情况,第二个重载是无用的。
With the original code, which has just one overload for Load
, this function is called for lvalues and rvalues.
对于只有一个 重载的原始代码,Load
该函数被调用用于左值和右值。
With the new code, the first overload is called for lvalues and the second is called for rvalues. However, the second overload calls the first one. At the end, the effect of calling one or the other implies that the same operation (whatever the first overload does) will be performed.
使用新代码,第一个重载被调用用于左值,第二个被调用用于右值。但是,第二个重载调用第一个。最后,调用一个或另一个的效果意味着将执行相同的操作(无论第一个重载做什么)。
Therefore, the effects of the original code and the new code are the same but the first code is just simpler.
因此,原代码和新代码的效果是一样的,只是第一个代码更简单。
Deciding whether a function must take an argument by value, lvalue reference or rvalue reference depends very much on what it does. You should provide an overload taking rvalue references when you want to move the passed argument. There are several good referenceson move semantincs out there, so I won't cover it here.
决定一个函数是否必须通过值、左值引用或右值引用来接受参数在很大程度上取决于它的作用。当您想要移动传递的参数时,您应该提供一个采用右值引用的重载。有几个关于移动语义的很好的参考资料,所以我不会在这里介绍。
Bonus:
奖金:
To help me make my point consider this simple probe
class:
为了帮助我表达我的观点,请考虑这个简单的probe
类:
struct probe {
probe(const char* ) { std::cout << "ctr " << std::endl; }
probe(const probe& ) { std::cout << "copy" << std::endl; }
probe(probe&& ) { std::cout << "move" << std::endl; }
};
Now consider this function:
现在考虑这个函数:
void f(const probe& p) {
probe q(p);
// use q;
}
Calling f("foo");
produces the following output:
调用f("foo");
产生以下输出:
ctr
copy
No surprises here: we create a temporary probe
passing the const char*
"foo"
. Hence the first output line. Then, this temporary is bound to p
and a copy q
of p
is created inside f
. Hence the second output line.
毫无意外:我们创建了一个临时probe
传递const char*
"foo"
. 因此是第一条输出线。然后,这个临时势必p
和副本q
的p
创建中f
。因此,第二个输出线。
Now, consider taking p
by value, that is, change f
to:
现在,考虑p
按值获取,即更改f
为:
void f(probe p) {
// use p;
}
The output of f("foo");
is now
f("foo");
现在的输出是
ctr
Some will be surprised that in this case: there's no copy! In general, if you take an argument by reference and copy it inside your function, then it's better to take the argument by value. In this case, instead of creating a temporary and copying it, the compiler can construct the argument (p
in this case) direct from the input ("foo"
). For more information, see Want Speed? Pass by Value.by Dave Abrahams.
在这种情况下,有些人会感到惊讶:没有副本!通常,如果您通过引用获取参数并将其复制到您的函数中,那么最好按值获取参数。在这种情况下,编译器可以p
直接从输入 ( "foo"
)构造参数(在这种情况下),而不是创建临时文件并复制它。有关更多信息,请参阅想要速度?按值传递。通过戴夫亚伯拉罕。
There are two notable exceptions to this guideline: constructors and assignment operators.
该指南有两个值得注意的例外:构造函数和赋值运算符。
Consider this class:
考虑这个类:
struct foo {
probe p;
foo(const probe& q) : p(q) { }
};
The constructor takes a probe
by const reference and then copy it to p
. In this case, following the guideline above doesn't bring any performance improvement and probe
's copy constructor will be called anyway. However, taking q
by value might create an overload resolution issue similar to the one with assignment operator that I shall cover now.
构造函数接受一个probe
by const 引用,然后将其复制到p
. 在这种情况下,遵循上述指南不会带来任何性能改进,并且probe
无论如何都会调用 的复制构造函数。但是,q
按值获取可能会产生类似于我现在将介绍的带有赋值运算符的重载解析问题。
Suppose that our class probe
has a non-throwing swap
method. Then the suggested implementation of its assignment operator (thinking in C++03 terms for the time being) is
假设我们的类probe
有一个非抛出swap
方法。然后其赋值运算符的建议实现(暂时以 C++03 术语思考)是
probe& operator =(const probe& other) {
probe tmp(other);
swap(tmp);
return *this;
}
Then, according to the guideline above, it's better to write it like this
然后,根据上面的指南,最好这样写
probe& operator =(probe tmp) {
swap(tmp);
return *this;
}
Now enter C++11 with rvalue references and move semantics. You decided to add a move assignment operator:
现在输入带有右值引用和移动语义的 C++11。您决定添加一个移动赋值运算符:
probe& operator =(probe&&);
Now calling the assignment operator on a temporary creates an ambiguity because both overloads are viable and none is preferred over the other. To resolve this issue, use the original implementation of the assignment operator (taking the argument by const reference).
现在在临时调用赋值运算符会产生歧义,因为两个重载都是可行的,并且没有一个优先于另一个。要解决此问题,请使用赋值运算符的原始实现(通过常量引用获取参数)。
Actually, this issue is not particular to constructors and assignment operators and might happen with any function. (It's more likely that you will experience it with constructors and assignment operators though.) For instance, calling g("foo");
when g
has the following two overloads raises the ambiguity:
实际上,这个问题并不是构造函数和赋值运算符所特有的,任何函数都可能发生。(不过,您更有可能使用构造函数和赋值运算符来体验它。)例如,调用g("foo");
wheng
有以下两个重载会增加歧义:
void g(probe);
void g(probe&&);
回答by user657267
Unless you're doing something other than calling the lvalue reference version of Load
, you don't need the second function, as an rvalue will bind to a const lvalue reference.
除非您正在执行除调用 的左值引用版本之外的其他操作,否则您Load
不需要第二个函数,因为右值将绑定到 const 左值引用。
回答by Tim Rae
Since we know most of the time the Path is a temporary rvalue, does it make sense to add an Rvalue version of this method?
既然我们知道大多数时候 Path 是一个临时的右值,那么添加这个方法的右值版本有意义吗?
Probably not... Unless you need to do something tricky inside Load()
that requires a non-const parameter. For example, maybe you want to std::move(Path)
into another thread. In that case it might make sense to use move semantics.
可能不会......除非你需要在内部做一些Load()
需要非常量参数的棘手事情。例如,也许您想std::move(Path)
进入另一个线程。在这种情况下,使用移动语义可能是有意义的。
Is this a correct approach to writing rvalue versions of methods?
这是编写方法的右值版本的正确方法吗?
No, you should do it the other way around:
不,你应该反过来做:
void Asset::load( const std::string& path )
{
auto path_copy = path;
load(std::move(path_copy)); // call the below method
}
void Asset::load( std::string&& path )
{
// complicated method....
}
回答by Red XIII
It's generally a question of whether internally you will make a copy (explicitly, or implicitly) of the incoming object (provide T&&
argument), or you will just use it (stick to [const] T&
).
这通常是一个问题,您是否会在内部复制(显式或隐式)传入对象(提供T&&
参数),或者您只是使用它(坚持[const] T&
)。
回答by Tony Delroy
If your Load
member function doesn't assign from the incoming string, you should simply provide void Asset::Load(const std::string& Path)
.
如果您的Load
成员函数没有从传入的字符串中分配,您应该简单地提供void Asset::Load(const std::string& Path)
.
If you do assign from the incoming path
, say to a member variable, then there's a scenario where it could be slightly more efficient to provide void Asset::Load(std::string&& Path)
too, but you'd need a different implementation that assigns ala loaded_from_path_ = std::move(Path);
.
如果您确实从传入的path
,例如成员变量进行分配,那么在void Asset::Load(std::string&& Path)
某些情况下,提供也可能稍微更有效,但是您需要一个不同的实现来分配 ala loaded_from_path_ = std::move(Path);
。
The potential benefit is to the caller, in that with the &&
version they might receive the free-store region that had been owned by the member variable, avoiding a pessimistic delete[]
ion of that buffer inside void Asset::Load(const std::string& Path)
and possible re-allocation next time the caller's string is assigned to (assuming the buffer's large enough to fit its next value too).
潜在的好处是对调用者来说,因为&&
他们可能会收到成员变量拥有的自由存储区域的版本,避免delete[]
内部缓冲区的悲观离子void Asset::Load(const std::string& Path)
以及下次分配调用者的字符串时可能重新分配to(假设缓冲区足够大以适应其下一个值)。
In your stated scenario, you're usually passing in string literals; such caller's will get no benefit from any &&
overload as there's no caller-owned std::string
instance to receive the existing data member's buffer.
在您陈述的场景中,您通常会传入字符串文字;这样的调用者不会从任何&&
重载中受益,因为没有调用者拥有的std::string
实例来接收现有数据成员的缓冲区。