use*_*677 97 c++ move-semantics c++11
我看到某个地方有人决定复制一个对象并随后将其移动到一个类的数据成员的代码.这使我感到困惑,因为我认为移动的重点是避免复制.这是一个例子:
struct S
{
S(std::string str) : data(std::move(str))
{}
};
Run Code Online (Sandbox Code Playgroud)
这是我的问题:
str
?std::string
吗?And*_*owl 97
在我回答你的问题之前,有一件事你似乎错了:在C++ 11中按价值取得并不总是意味着复制.如果传递了一个右值,那么它将被移动(假设存在一个可行的移动构造函数)而不是被复制.并且std::string
有一个移动构造函数.
与C++ 03不同,在C++ 11中,按值获取参数通常是惯用的,原因我将在下面解释.另请参阅StackOverflow上的此问答,以获取有关如何接受参数的更一般的指导原则.
为什么我们不采用右值引用
str
?
因为那样就不可能传递左值,例如:
std::string s = "Hello";
S obj(s); // s is an lvalue, this won't compile!
Run Code Online (Sandbox Code Playgroud)
如果S
只有一个接受rvalues的构造函数,则上面的代码不能编译.
副本不会很贵,特别是给出类似的东西
std::string
吗?
如果你通过一个右值,将被移动到str
,并最终将被移入data
.不会进行复制.另一方面,如果传递左值,则将左值复制到左侧,str
然后移入左侧data
.
总而言之,对于左值,两个动作为左值,一个副本和一个左手动作.
作者决定复制然后移动的原因是什么?
首先,正如我上面提到的,第一个并不总是副本; 而这说,答案是:" 因为它是有效的(std::string
物品移动便宜)而且简单 ".
假设移动很便宜(这里忽略了SSO),在考虑这种设计的整体效率时,它们几乎可以被忽视.如果我们这样做,我们就有一个lvalues副本(正如我们接受左值引用时那样const
)和rvalues没有副本(如果我们接受了左值引用,我们仍然会有副本const
).
这意味着按值提取与提供左值const
时的左值引用一样好,并且在提供右值时更好.
Yak*_*ont 51
要理解为什么这是一个好的模式,我们应该检查C++ 03和C++ 11中的替代方案.
我们采用C++ 03方法std::string const&
:
struct S
{
std::string data;
S(std::string const& str) : data(str)
{}
};
Run Code Online (Sandbox Code Playgroud)
在这种情况下,将始终执行单个副本.如果从原始C字符串std::string
构造,将构造一个,然后再次复制:两个分配.
有一个C++ 03方法来引用a std::string
,然后将其交换到本地std::string
:
struct S
{
std::string data;
S(std::string& str)
{
std::swap(data, str);
}
};
Run Code Online (Sandbox Code Playgroud)
这是"移动语义"的C++ 03版本,并且swap
通常可以优化为非常便宜(非常像a move
).它也应该在上下文中分析:
S tmp("foo"); // illegal
std::string s("foo");
S tmp2(s); // legal
Run Code Online (Sandbox Code Playgroud)
并强迫你形成一个非临时的std::string
,然后丢弃它.(临时std::string
不能绑定到非const引用).但是,只完成了一次分配.C++ 11版本需要一个&&
并要求您使用std::move
或使用临时调用它:这要求调用者在调用之外显式创建副本,并将该副本移动到函数或构造函数中.
struct S
{
std::string data;
S(std::string&& str): data(std::move(str))
{}
};
Run Code Online (Sandbox Code Playgroud)
使用:
S tmp("foo"); // legal
std::string s("foo");
S tmp2(std::move(s)); // legal
Run Code Online (Sandbox Code Playgroud)
接下来,我们可以做完整的C++ 11版本,它同时支持copy和move
:
struct S
{
std::string data;
S(std::string const& str) : data(str) {} // lvalue const, copy
S(std::string && str) : data(std::move(str)) {} // rvalue, move
};
Run Code Online (Sandbox Code Playgroud)
然后我们可以检查它是如何使用的:
S tmp( "foo" ); // a temporary `std::string` is created, then moved into tmp.data
std::string bar("bar"); // bar is created
S tmp2( bar ); // bar is copied into tmp.data
std::string bar2("bar2"); // bar2 is created
S tmp3( std::move(bar2) ); // bar2 is moved into tmp.data
Run Code Online (Sandbox Code Playgroud)
很明显,这种2重载技术至少与上述两种C++ 03样式一样有效,甚至更高效.我将这个2重载版本称为"最优"版本.
现在,我们将检查副本版本:
struct S2 {
std::string data;
S2( std::string arg ):data(std::move(x)) {}
};
Run Code Online (Sandbox Code Playgroud)
在每个场景中:
S2 tmp( "foo" ); // a temporary `std::string` is created, moved into arg, then moved into S2::data
std::string bar("bar"); // bar is created
S2 tmp2( bar ); // bar is copied into arg, then moved into S2::data
std::string bar2("bar2"); // bar2 is created
S2 tmp3( std::move(bar2) ); // bar2 is moved into arg, then moved into S2::data
Run Code Online (Sandbox Code Playgroud)
如果您将此并排与"最佳"版本进行比较,我们会做一个额外的move
!我们不是一次额外的copy
.
因此,如果我们假设它move
很便宜,那么这个版本可以获得与最优版本几乎相同的性能,但是代码减少了2倍.
如果你说的是2到10个参数,代码的减少是指数的 - 1个参数减少2倍,2个8倍,3个16倍,4个1024x个10个参数.
现在,我们可以通过完美的转发和SFINAE来解决这个问题,允许你编写一个带有10个参数的构造函数或函数模板,SFINAE确保参数是合适的类型,然后将它们移动或复制到当地的州根据要求.虽然这可以防止程序大小问题的千倍增加,但仍然可以从该模板生成一大堆函数.(模板函数实例化生成函数)
许多生成的函数意味着更大的可执行代码大小,这本身可以降低性能.
对于几个move
s 的成本,我们得到更短的代码和几乎相同的性能,并且通常更容易理解代码.
现在,这只能起作用,因为我们知道,当调用函数(在本例中是一个构造函数)时,我们将需要该参数的本地副本.我们的想法是,如果我们知道我们将要制作副本,我们应该让调用者知道我们正在通过将它放入我们的参数列表来制作副本.然后他们可以围绕这样一个事实进行优化:他们将给我们一份副本(例如,通过我们的论证).
"通过值获取"技术的另一个优点是,通常移动构造函数是noexcept.这意味着采用按值并移出其参数的函数通常可以是noexcept,将任何throw
s移出其体并进入调用范围(有时可以通过直接构造来避免它,或者构造项目和move
参数,以控制投掷的位置).制定方法并不值得.
Phi*_*ßen 11
你不想通过编写移动的构造函数和复制的构造函数来重复自己:
S(std::string&& str) : data(std::move(str)) {}
S(const std::string& str) : data(str) {}
Run Code Online (Sandbox Code Playgroud)
这是很多样板代码,特别是如果你有多个参数.您的解决方案避免了重复不必要的移动成本.(然而,移动操作应该相当便宜.)
竞争成语是使用完美转发:
template <typename T>
S(T&& str) : data(std::forward<T>(str)) {}
Run Code Online (Sandbox Code Playgroud)
模板魔术将根据您传入的参数选择移动或复制.它基本上扩展到第一个版本,其中两个构造函数都是手动编写的.有关背景信息,请参阅Scott Meyer关于通用引用的帖子.
从性能方面来说,完美转发版本优于您的版本,因为它避免了不必要的移动.但是,有人可能会认为您的版本更易于阅读和编写.无论如何,在大多数情况下,可能的性能影响无关紧要,因此最终似乎是一种风格问题.
归档时间: |
|
查看次数: |
7736 次 |
最近记录: |