我试图理解移动语义、右值引用、std::move
,等等。通过搜索本网站上的各种问题,我试图弄清楚为什么要传递const std::string
如果我理解正确,以下内容需要单个副本(通过构造函数)加上移动(从临时副本移动到成员):
Dog::Dog(std::string name) : _name(std::move(name)) {}
另一种(也是老式的)方法是通过引用传递并复制(从引用到成员):
Dog::Dog(const std::string &name) : _name(name) {}
如果第一种方法需要同时复制和移动,而第二种方法只需要一个副本,那么第一种方法怎么可能是首选的,在某些情况下,速度更快?
先回答简短的问题:康斯特打电话
lvalue rvalue unused lvalue unused rvalue
------------------------------------------------------
const& copy copy - -
rvalue&& - move - -
value copy, move move copy -
T&& copy move - -
overload copy move - -
因此,我的执行摘要是,如果
考虑一个用于复制其参数的函数
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
考虑用LValk和RValk调用各种选项:
Dog::Dog(const std::string &name) : _name(name) {}
无论是用左值还是右值调用,这都需要一个副本,以便从名称
初始化名称
。移动不是一个选项,因为name
是const
。
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*
调用时,将进行一次长度计算和一次副本。
在使用数据时,您需要一个可以使用的对象。当你得到一个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