如果const引用仅花费一个副本,为什么在C++ 11中推荐使用值传递(如果需要副本)?

Joh*_*ata 33 c++ move-semantics c++11

我试图理解移动语义,右值引用std::move等等.我一直试图通过搜索本网站上的各种问题来解决,如果需要复制,为什么传递const std::string &name+ _name(name)不如std::string name+ + _name(std::move(name)).

如果我理解正确,下面需要一个副本(通过构造函数)加上一个移动(从临时到成员):

Dog::Dog(std::string name) : _name(std::move(name)) {}
Run Code Online (Sandbox Code Playgroud)

替代(和老式)方法是通过引用传递它并将其复制(从引用到成员):

Dog::Dog(const std::string &name) : _name(name) {}
Run Code Online (Sandbox Code Playgroud)

如果第一种方法需要复制并同时移动两种方法,而第二种方法只需要一个副本,那么第一种方法如何首选,在某些情况下更快?

Die*_*ühl 31

使用数据时,您需要一个可以使用的对象.当你得到a时,std::string const&不得不独立于是否需要参数来复制对象.

当对象按值传递时,如果必须复制对象,则对象将被复制,即,当传递的对象不是临时对象时.但是,如果它恰好是临时的,则可以在适当的位置构建对象,即,任何副本都可能已被删除,您只需支付移动结构.也就是说,有可能实际上没有副本发生.

  • 还有第三种情况; 如果传入的对象是通过右值引用(即std move的返回值).然后by-value执行2次移动和0次复制,而const ref执行1次复制. (8认同)

Jef*_*ett 27

考虑使用左值和右值调用各种选项:

  1. Dog::Dog(const std::string &name) : _name(name) {}
    
    Run Code Online (Sandbox Code Playgroud)

    无论是所谓的与左值右值或者,这需要一份副本,初始化_namename.移动不是一种选择,因为nameconst.

  2. Dog::Dog(std::string &&name) : _name(std::move(name)) {}
    
    Run Code Online (Sandbox Code Playgroud)

    这只能用右值调用,它会移动.

  3.  Dog::Dog(std::string name) : _name(std::move(name)) {}
    
    Run Code Online (Sandbox Code Playgroud)

    使用左值调用时,将复制以传递参数,然后移动以填充数据成员.使用rvalue调用时,将移动以传递参数,然后移动以填充数据成员.在rvalue的情况下,可以省略移动以传递参数.因此,使用左值调用此结果会导致一个副本和一个移动,并使用右值调用此结果会导致一到两个移动.

最佳解决方案是定义(1)(2).解决方案(3)可以相对于最佳值进行额外移动.但是编写一个函数比编写两个几乎相同的函数更短,更易于维护,并且假设移动很便宜.

当使用可隐式转换为字符串的值进行调用时const char*,会发生隐式转换,其涉及长度计算和字符串数据的副本.然后我们陷入了右边的情况.在这种情况下,使用a string_view提供了另一种选择:

  1. Dog::Dog(std::string_view name) : _name(name) {}
    
    Run Code Online (Sandbox Code Playgroud)

    使用字符串左值或右值调用时,会产生一个副本.当用a调用时const char*,进行一次长度计算和一次复制.


DrS*_*Hay 7

简短回答:由const调用并且总是花费一份副本.根据条件,按值调用可能只需要一次移动.但这取决于(请参阅下面的代码示例,了解此表所引用的场景):

            lvalue        rvalue      unused lvalue  unused rvalue
            ------------------------------------------------------
const&      copy          copy        -              -
rvalue&&    -             move        -              -
value       copy, move    move        copy           - 
T&&         copy          move        -              -
overload    copy          move        -              - 
Run Code Online (Sandbox Code Playgroud)

因此,我的执行摘要将是值得考虑的价值

  • 移动很便宜,因为可能会有额外的举动
  • 该参数是无条件使用的.如果不使用参数,则按值调用也会花费一份副本,例如因为if子句或某事物.

按价值呼叫

考虑一个用于复制其参数的函数

class Dog {
public:
    void name_it(const std::string& newName) { names.push_back(newName); }
private:
    std::vector<std::string> names;
};
Run Code Online (Sandbox Code Playgroud)

如果传递了左值name_it,则在rvalue的情况下,您将进行两次复制操作.这很糟糕,因为右手可以让我感动.

一种可能的解决方案是为rvalues写一个重载:

class Dog {
public:
    void name_it(const std::string& newName) { names.push_back(newName); }
    void name_it(std::string&& newName) { names.push_back(std::move(newName)); }
private:
    std::vector<std::string> names;
};
Run Code Online (Sandbox Code Playgroud)

这解决了问题,一切都很好,尽管你有两个代码两个函数具有完全相同的代码.

另一个可行的解决方案是使用完美转发,但也有一些缺点,(例如,完美的转发功能非常贪婪,并且使现有的重载const和函数无用,通常它们需要在头文件中,它们创建了几个函数目标代码等等.)

class Dog {
public:
    template<typename T>
    void name_it(T&& in_name) { names.push_back(std::forward<T>(in_name)); }
private:
    std::vector<std::string> names;
};
Run Code Online (Sandbox Code Playgroud)

另一种解决方案是使用按值调用:

class Dog {
public:
    void name_it(std::string newName) { names.push_back(std::move(newName)); }
private:
    std::vector<std::string> names;
};
Run Code Online (Sandbox Code Playgroud)

重要的是,正如你提到的那样std::move.这样,rvalue和lvalue都有一个函数.您将移动右值但接受左值的额外移动,如果移动便宜并且无论条件如何都复制或移动参数,这可能没问题.

所以最后我真的认为推荐一种方式胜过其他方式是完全错误的.这很大程度上取决

#include <vector>
#include <iostream>
#include <utility>

using std::cout;

class foo{
public:
    //constructor
    foo()  {}
    foo(const foo&)  { cout << "\tcopy\n" ; }
    foo(foo&&)  { cout << "\tmove\n" ; }
};

class VDog {
public:
    VDog(foo name) : _name(std::move(name)) {}
private:
    foo _name;
};

class RRDog {
public:
    RRDog(foo&& name) : _name(std::move(name)) {}
private:
    foo _name;
};

class CRDog {
public:
    CRDog(const foo& name) : _name(name) {}
private:
    foo _name;
};

class PFDog {
public:
    template <typename T>
    PFDog(T&& name) : _name(std::forward<T>(name)) {}
private:
    foo _name;
};

//
volatile int s=0;

class Dog {
public:
    void name_it_cr(const foo& in_name) { names.push_back(in_name); }
    void name_it_rr(foo&& in_name)   { names.push_back(std::move(in_name));}

    void name_it_v(foo in_name) { names.push_back(std::move(in_name)); }
    template<typename T>
    void name_it_ur(T&& in_name) { names.push_back(std::forward<T>(in_name)); }
private:
    std::vector<foo> names;
};


int main()
{
    std::cout << "--- const& ---\n";
    {
        Dog a,b;
        foo my_foo;
        std::cout << "lvalue:";
        a.name_it_cr(my_foo);
        std::cout << "rvalue:";
        b.name_it_cr(foo());
    }
    std::cout << "--- rvalue&& ---\n";
    {
        Dog a,b;
        foo my_foo;
        std::cout << "lvalue: -\n";
        std::cout << "rvalue:";
        a.name_it_rr(foo());
    }
    std::cout << "--- value ---\n";
    {
        Dog a,b;
        foo my_foo;
        std::cout << "lvalue:";
        a.name_it_v(my_foo);
        std::cout << "rvalue:";
        b.name_it_v(foo());
    }
    std::cout << "--- T&&--\n";
    {
        Dog a,b;
        foo my_foo;
        std::cout << "lvalue:";
        a.name_it_ur(my_foo);
        std::cout << "rvalue:";
        b.name_it_ur(foo());
    }


    return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出:

--- const& ---
lvalue: copy
rvalue: copy
--- rvalue&& ---
lvalue: -
rvalue: move
--- value ---
lvalue: copy
    move
rvalue: move
--- T&&--
lvalue: copy
rvalue: move
Run Code Online (Sandbox Code Playgroud)