当前位置: 首页 > 知识库问答 >
问题:

如果一个常量引用也只需要一个副本,为什么在C 11中推荐按值传递(如果需要副本)?

黄英韶
2023-03-14

我试图理解移动语义、右值引用、std::move,等等。通过搜索本网站上的各种问题,我试图弄清楚为什么要传递const std::string

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

Dog::Dog(std::string name) : _name(std::move(name)) {}

另一种(也是老式的)方法是通过引用传递并复制(从引用到成员):

Dog::Dog(const std::string &name) : _name(name) {}

如果第一种方法需要同时复制和移动,而第二种方法只需要一个副本,那么第一种方法怎么可能是首选的,在某些情况下,速度更快?


共有3个答案

戚同
2023-03-14

先回答简短的问题:康斯特打电话

            lvalue        rvalue      unused lvalue  unused rvalue
            ------------------------------------------------------
const&      copy          copy        -              -
rvalue&&    -             move        -              -
value       copy, move    move        copy           - 
T&&         copy          move        -              -
overload    copy          move        -              - 

因此,我的执行摘要是,如果

  • 搬家很便宜,因为可能会有额外的搬家
  • 参数是无条件使用的。如果由于if子句或其他原因未使用参数,则按值调用也需要一份副本

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

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

如果左值传递到name_it,则右值也有两个复制操作。这很糟糕,因为右值可能会移动。

一种可能的解决方案是为右值编写重载:

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;
};

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

另一个可行的解决方案是使用完美转发,但这也有几个缺点,(例如,完美转发函数非常贪婪,并使现有的过载常量

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;
};

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

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

重要的是,正如你提到的d::移动。这样,右值和左值都有一个函数。您将移动右值,但接受左值的额外移动,如果移动很便宜,并且您复制或移动参数而不管条件如何,这可能是好的。

所以最后,我真的认为推荐一种方法而不是其他方法是完全错误的。这非常取决于。

#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;
}

输出:

--- const& ---
lvalue: copy
rvalue: copy
--- rvalue&& ---
lvalue: -
rvalue: move
--- value ---
lvalue: copy
    move
rvalue: move
--- T&&--
lvalue: copy
rvalue: move
郝哲茂
2023-03-14

考虑用LValk和RValk调用各种选项:

  1. Dog::Dog(const std::string &name) : _name(name) {}
    

    无论是用左值还是右值调用,这都需要一个副本,以便从名称初始化名称。移动不是一个选项,因为nameconst

    Dog::Dog(std::string &&name) : _name(std::move(name)) {}
    

    只能使用右值调用它,它将移动。

     Dog::Dog(std::string name) : _name(std::move(name)) {}
    

    当使用左值调用时,它将复制以传递参数,然后移动以填充数据成员。当使用右值调用时,它将移动以传递参数,然后移动以填充数据成员。在右值的情况下,移动以通过参数可能会被忽略。因此,使用左值调用此函数将导致一次复制和一次移动,使用右值调用此函数将导致一到两次移动。

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

    当使用隐式转换为字符串(如const char*)的值调用时,会发生隐式转换,其中涉及长度计算和字符串数据的副本。然后我们进入右值的情况。在这种情况下,使用string\u视图提供了另一个选项:

    Dog::Dog(std::string_view name) : _name(name) {}
    

    当使用字符串左值或右值调用时,将生成一个副本。当使用常量char*调用时,将进行一次长度计算和一次副本。

阳俊德
2023-03-14

在使用数据时,您需要一个可以使用的对象。当你得到一个std::string常量

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

 类似资料:
  • 这里是SQL初学者,在我的大学课程中,我们有以下模式。 学生(snum:整数,sname:字符串,专业:字符串,级别:字符串,年龄:整数) 类(名称:字符串,meets_at:字符串,房间:字符串,fid:整数) 已注册(snum:整数,cname:字符串) 教员(fid:整数,fname:字符串,deptid:整数) 其中一个练习如下: 找出同时参加两个班的所有学生的名字。 下面是它的SQL语句

  • 我正在检查Angular Bootstrap UI,特别是服务并注意到一件有趣的事情。 在他们的示例中,'http://plnkr.co/edit/e5xykpqwytsljua6fxwt?p=preview',在附加到弹出窗口的控制器中,他们将选定的项包含到另一个内部属性中 为什么需要这个?JavaScript找到了什么? THX

  • 问题内容: 如果我们使用ExecutorCompletionService,则可以将一系列任务作为s 提交,并将结果作为进行交互。 但也有在的,它接受一个任务,我们得到的名单,以检索结果。 据我所知,使用一个或多个都不会有任何好处(除了我们避免使用循环,否则我们将不得不对任务进行操作),并且基本上它们是相同的想法,只是稍有不同。 那么,为什么有两种不同的方式提交一系列任务呢?我在性能上正确吗?有没

  • 我正在阅读有关java中的同步概念的信息,并遇到了同步语句。 我想知道,为什么我们向它传递参数,尽管它看起来像静态块(这只是一个例子),并且传递的参数没有指定任何数据类型。 例: 如果有人知道,请解释。

  • 我有一个标准差,平均值和样本量。我需要创建一个循环,将产生5000个样本的意思 我该怎么做?

  • 问题内容: 您好,我正在构建Spring-Hibernate应用程序。我真的需要从下面进行配置吗? 我已经在我的root-context.xml中设置了注释驱动 现在是否不应该使用注释@Entity自动hibernate此包中的所有内容并将其转换为table?就目前而言,没有annotatedClasses的他不会从实体创建表 问题答案: 使用 文档,卢克! […] AnnotationSessi