正确使用右值引用作为参数

Gra*_*pes 52 c++ c++11

我们以下面的方法为例:

void Asset::Load( const std::string& path )
{
    // complicated method....
}
Run Code Online (Sandbox Code Playgroud)

这种方法的一般用途如下:

Asset exampleAsset;
exampleAsset.Load("image0.png");
Run Code Online (Sandbox Code Playgroud)

由于我们大多数时候都知道Path是一个临时右值,所以添加这个方法的Rvalue版本是否有意义?如果是这样,这是一个正确的实施;

void Asset::Load( const std::string& path )
{
    // complicated method....
}
void Asset::Load( std::string&& path )
{
     Load(path); // call the above method
}
Run Code Online (Sandbox Code Playgroud)

这是编写rvalue版本方法的正确方法吗?

Cas*_*eri 62

对于您的特定情况,第二次重载是无用的.

使用只有一个重载的原始代码,Load为lvalues和rvalues调用此函数.

使用新代码,第一个重载调用lvalues,第二个重载调用rvalues.但是,第二个重载调用第一个重载.最后,调用一个或另一个的效果意味着将执行相同的操作(无论第一次重载是什么).

因此,原始代码和新代码的效果是相同的,但第一个代码更简单.

确定函数是否必须按值,左值引用或右值引用取一个参数,在很大程度上取决于它的作用.当你想移动传递的参数时,你应该提供一个带右值引用的重载.关于移动semantincs 有几个很好的参考,所以我不会在这里介绍它.

奖金:

为了帮助我,请考虑这个简单的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; }
};
Run Code Online (Sandbox Code Playgroud)

现在考虑这个功能:

void f(const probe& p) {
    probe q(p);
    // use q;
}
Run Code Online (Sandbox Code Playgroud)

调用f("foo");产生以下输出:

ctr
copy
Run Code Online (Sandbox Code Playgroud)

这里没有惊喜:我们创造了一个临时的probe传递const char* "foo".因此第一条输出线.然后,这个临时势必p和副本qp创建中f.因此第二个输出线.

现在,考虑p按价值计算,即f改为:

void f(probe p) {
    // use p;
}
Run Code Online (Sandbox Code Playgroud)

输出f("foo");现在是

ctr
Run Code Online (Sandbox Code Playgroud)

在这种情况下,有些人会感到惊讶:没有副本!通常,如果您通过引用获取参数并将其复制到函数内部,那么最好按值获取参数.在这种情况下,编译器可以p直接从input("foo")构造参数(在本例中),而不是创建临时和复制它.有关更多信息,请参阅想要速度?通过价值.戴夫亚伯拉罕.

本指南有两个值得注意的例外:构造函数和赋值运算符.

考虑这个课程:

struct foo {
    probe p;
    foo(const probe& q) : p(q) { }
};
Run Code Online (Sandbox Code Playgroud)

构造函数采用probeby const引用,然后将其复制到p.在这种情况下,遵循上面的准则并没有带来任何性能改进,probe无论如何都会调用复制构造函数.但是,q按值获取可能会产生一个类似于赋值运算符的重载决策问题,我现在将介绍它.

假设我们的班级probe有一个非投掷swap方法.然后建议的赋值运算符的实现(暂时用C++ 03术语思考)是

probe& operator =(const probe& other) {
    probe tmp(other);
    swap(tmp);
    return *this;
}
Run Code Online (Sandbox Code Playgroud)

然后,根据上面的指导原则,最好这样写

probe& operator =(probe tmp) {
    swap(tmp);
    return *this;
}
Run Code Online (Sandbox Code Playgroud)

现在输入带有右值引用的C++ 11并移动语义.您决定添加移动赋值运算符:

probe& operator =(probe&&);
Run Code Online (Sandbox Code Playgroud)

现在,临时调用赋值运算符会产生歧义,因为两个重载都是可行的,并且没有一个优先于另一个.要解决此问题,请使用赋值运算符的原始实现(通过const引用获取参数).

实际上,这个问题并不特定于构造函数和赋值运算符,并且可能会出现在任何函数中.(更可能的是,您将使用构造函数和赋值运算符来体验它.)例如,调用g("foo");何时g具有以下两个重载会引起歧义:

void g(probe);
void g(probe&&);
Run Code Online (Sandbox Code Playgroud)


use*_*267 7

除非您正在执行除调用左值引用版本之外的其他Load操作,否则不需要第二个函数,因为右值将绑定到const左值引用.


Tim*_*Rae 7

由于我们知道大多数时候 Path 是临时右值,因此添加此方法的右值版本是否有意义?

可能不是......除非你需要在内部做一些Load()需要非常量参数的棘手事情。例如,也许您想std::move(Path)进入另一个线程。在这种情况下,使用移动语义可能有意义。

这是编写方法的右值版本的正确方法吗?

不,你应该反过来做:

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....
}
Run Code Online (Sandbox Code Playgroud)