我想知道从此示例中获取的以下代码:
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);
}
Run Code Online (Sandbox Code Playgroud)
我了解std::move()基本的工作原理,但在幕后有一些东西对我来说没有意义。
例如,如果NewStruct是在本地堆栈上创建的,如示例中所示。如果我在退出函数作用域之前使用移动语义来保存其数据免遭破坏,那么NewStruct被破坏就不会影响我的数据,这很好。但是这个信息不是还是放在栈里的吗?NewStruct如果我要再次扩展堆栈的使用,那么一旦堆栈增长并想要覆盖最初创建数据的位置,为什么这些信息不会面临被覆盖的危险呢?
添加第二个例子也许可以更好地表达我的观点:
void MyFunc(vector<char*> &vecCharPointers)
{
char* myStr = {'H', 'e', 'l', 'l', 'o'};
vecCharPointers.push_back(std::move(myStr));
}
int main()
{
vector<char*> vecCharPointers;
char* cp = nullptr;
}
Run Code Online (Sandbox Code Playgroud)
我认为您的误解在于数据的实际存储方式。结构体对象将在堆栈上,包含一个int和一个std::string对象,是的。但是,实际的字符串内容存储在空闲存储中,而不是堆栈中(忽略此处不重要的优化)。
现在让我们std::move从图片中删除并移动语义。会发生什么?该向量将在末尾创建一个新元素,该元素是从堆栈变量复制构造的。这意味着新对象将拥有值的副本int和值的副本std::string。复制std::string需要在空闲存储上分配另一个内存块并将数据复制到那里。此外,任何成员(例如尺寸)都会按您的原样复制int。当堆栈变量超出范围时,它的 int 和字符串将被销毁,这会导致字符串自行清理,而不以任何方式触及新副本。之后剩下的是向量末尾的新对象及其自己的数据副本。
如果你能原谅这个糟糕的图表:
搬家如何改变这一点?移动只是一种优化,目的是尽可能避免不必要的复制。复制是一种有效的移动策略,但如果你能做得更好,那就不是一个好的策略。使用std::move最终会导致向量在向量末尾创建新对象时移动堆栈对象。int 仍将被复制,因为那里没有优化。但是,字符串可以利用不再需要此免费存储数据的事实。新对象可以简单地窃取指针、复制大小等,并告诉移出的对象不要清理该空闲存储数据(转移所有权)。
如果您能原谅原始糟糕图表的稍微修改版本:
我们不再为字符串分配一个单独的块,但仅此而已。其他数据都还是复制的。当向量元素被删除或向量被破坏时,实际的字符串数据将被向量元素清理。堆栈元素现在没有什么需要清理的,因为移动“窃取”了字符串数据而不是复制它。您可以将其视为将堆栈对象的指针清空,即使实现可以自由地以不同的方式表示空字符串。
我所说的并不完全适用于此,因为 的主要实现std::string足够聪明,可以避免小字符串的额外分配。这仅仅意味着字符串数据需要被复制,因为正如你所说,当原始对象复制时它就会消失。不过,任何额外的分配都可以进行移动优化。
至于您的第二个示例,您无法进行一般优化来移动原始指针;(通常)只需复制 4 或 8 个字节。将其移动到向量中会复制指针值,从而在函数结束后导致向量内出现悬空指针。myStr当函数结束时,函数内的指针将被销毁,并且不会以任何方式影响向量。
| 归档时间: |
|
| 查看次数: |
1953 次 |
| 最近记录: |