请考虑以下代码:
struct MyStruct
{
int iInteger;
string strString;
};
void MyFunc(vector<MyStruct>& vecStructs)
{
MyStruct NewStruct = { 8, "Hello" };
vecStructs.push_back(std::move(NewStruct));
}
int main()
{
vector<MyStruct> vecStructs;
MyFunc(vecStructs);
}
为什么这样做?
在调用MyFunc时,返回地址应放在当前线程的堆栈中。现在创建NewStruct对象,它也应该被放置在堆栈上。使用std :: move,我告诉编译器,我不打算再使用NewStruct引用了。他可以偷走记忆。 (push_back函数是具有移动语义的函数。)
但是当函数返回并且NewStruct超出范围时。即使编译器不会从堆栈中删除最初存在的结构占用的内存,他至少也要删除以前存储的返回地址。
这将导致碎片堆叠,未来的分配将覆盖&#34;移动&#34;存储器中。
有人可以向我解释一下吗?
编辑: 首先:非常感谢您的回答。 但是从我所学到的,我仍然无法理解,为什么以下不能像我期望的那样起作用:
struct MyStruct
{
int iInteger;
string strString;
string strString2;
};
void MyFunc(vector<MyStruct>& vecStructs)
{
MyStruct oNewStruct = { 8, "Hello", "Definetly more than 16 characters" };
vecStructs.push_back(std::move(oNewStruct));
// At this point, oNewStruct.String2 should be "", because its memory was stolen.
// But only when I explicitly create a move-constructor in the form which was
// stated by Yakk, it is really that case.
}
void main()
{
vector<MyStruct> vecStructs;
MyFunc(vecStructs);
}
答案 0 :(得分:19)
首先,std::move
不移动,std::forward
不转发。
std::move
是对右值参考的强制转换。按照惯例,rvalue引用被视为“允许您将数据移出的引用,因为调用者承诺它们实际上不再需要该数据”。
在围栏的另一侧,rvalue引用隐式绑定到std::move
(有时是前向)的返回值,临时对象,在某些情况下从函数返回本地时,以及使用时临时或移动物体的成员。
在使用右值参考的函数中发生的事情并不神奇。它无法在相关对象中直接声明存储 。然而,它可以撕掉它的内脏;它有权(按照惯例)弄乱其参数内部状态,如果它能以这种方式更快地完成操作。
现在,C ++会自动为你编写一些移动构造函数。
struct MyStruct
{
int iInteger;
string strString;
};
在这种情况下,它会写出大致如下的内容:
MyStruct::MyStruct( MyStruct&& other ) noexcept(true) :
iInteger( std::move(other.iInteger) ),
strString( std::move(other.strString) )
{}
也就是说,它会做一个以元素为基础的移动构造。
移动整数时,没有任何有趣的事情发生。弄乱源整数的状态没有任何好处。
当您移动std::string
时,我们会获得一些效率。 C ++标准描述了当您从一个std::string
移动到另一个std::string
时会发生什么。基本上,如果源std::string
正在使用堆,则堆存储将传输到目标std::string
。
这是C ++容器的一般模式;当你从它们移动时,他们窃取源容器的“堆分配”存储并在目的地中重复使用它。
请注意,来源std::string
仍然是std::string
,只有一个“胆量被撕掉”。大多数像容器这样的容器都是空的,我不记得MyStruct
是否提供了保证(可能不是由于SBO),而且它现在并不重要。
简而言之,当你从某个东西移开时,它的内存不会被“重用”,但它拥有的内存可以被重用。
在您的情况下,std::string
有MyStruct
可以使用堆分配的内存。可以将此堆分配的内存移动到std::vector
中存储的"Hello"
。
在兔子洞中走得更远,std::string
可能太短以至于SBO发生(小缓冲区优化),而move
根本不使用堆。对于这种特殊情况,由于.bye
,可能几乎没有性能提升。
答案 1 :(得分:10)
您的示例可以简化为:
vector<string> vec;
string str; // populate with a really long string
vec.push_back(std::move(str));
这仍然提出了一个问题,“是否可以移动本地堆栈变量。”它只是删除了一些无关的代码,使其更容易理解。
答案是肯定的。上述代码可以从std::move
中受益,因为std::string
- 至少如果内容足够大 - 将实际数据存储在堆,即使变量在堆栈上。
如果您不使用std::move()
,您可以期望上述代码复制str
的内容,这可能是任意大的内容。如果您使用std::move()
,则只会复制字符串的直接成员(移动不需要将旧位置“清零”),并且数据将在不进行修改或复制的情况下使用。
这基本上是它之间的区别:
char* str; // populate with a really long string
char* other = new char[strlen(str)+1];
strcpy(other, str);
VS
char* str; // populate with a really long string
char* other = str;
在这两种情况下,变量都在堆栈中。但数据不是。
如果你有一个真正所有数据都在堆栈上的情况,例如有效“小字符串优化”的std::string
或包含整数的结构,那么std::move()
会买什么都没有。