我正在与移动c'tors进行学习/刷新练习,我遇到了一些意想不到的事情。下面我有一个类
person
,其中包含 std::string m_name;
。我使用它作为复制/移动c'tors的测试类。
以下是快速参考的代码:
#include <iostream>
#include <vector>
class person
{
public:
std::string m_name;
explicit person(const std::string &name) : m_name(name)
{
std::cout << "created " << m_name << std::endl;
}
~person()
{
std::cout << "destroyed " << m_name << std::endl;
}
person(const person &other) : m_name(other.m_name)
{
m_name += ".copied";
std::cout << "copied " << other.m_name << " -> " << m_name << std::endl;
}
person(const person &&other) noexcept : m_name(std::move(other.m_name))
{
m_name += ".moved";
std::cout << "moved " << other.m_name << " -> " << m_name << std::endl;
}
};
int main()
{
std::vector<person> people;
people.reserve(10);
std::cout << "\ncopy bob (lvalue):" << std::endl;
person bob{"bob"};
people.push_back(bob);
std::cout << "\nmove fred (lvalue):" << std::endl;
person fred{"fred"};
people.push_back(std::move(fred));
std::cout << "\ntemp joe (rvalue):" << std::endl;
people.push_back(person{"joe"});
std::cout << "\nterminating:" << std::endl;
}
这给了我期望的输出(主要是,除了为什么
std::string
内容没有“移动”?):https://godbolt.org/z/-J_56i
然后我删除
std::vector
reserve
,以便 std::vector
在我添加元素时必须“增长”。现在我得到了一些我真的没想到的东西:https://godbolt.org/z/rS6-mj
现在我可以看到 bob 被复制,然后在添加 fred 时移动,然后在添加 joe 时再次移动。我的印象是,当
std::vector
必须重新分配空间时,它会“移动”。但我认为它进行了内存复制/移动,而不是逐个对象的复制/移动。我真的没想到它会调用移动构造函数。
现在如果我删除 move c'tor,我发现 bob 被复制了三遍!:https://godbolt.org/z/_BxnvU 这看起来效率很低。
来自cplusplus.com:
push_back()
在末尾添加元素 在向量的末尾添加一个新元素, 在当前的最后一个元素之后。 val 的内容被复制(或 移动)到新元素。
这有效地将容器大小增加一倍,从而导致 自动重新分配已分配的存储空间当且仅当 新的向量大小超过了当前向量的容量。
调整大小()
调整容器大小,使其包含 n 个元素。
如果n小于当前容器大小,则内容为 减少到前 n 个元素,删除超出的元素(并销毁 他们)。
如果n大于当前容器大小,则内容为 通过在末尾插入所需数量的元素来扩展以达到 n 的大小如果指定了 val,则新元素将初始化为 val 的副本,否则,它们将被值初始化。
如果n也大于当前容器容量,则自动 已分配的存储空间发生重新分配。
注意这个函数改变了容器的实际内容 通过插入或删除其中的元素。
我想它并没有真正描述它“如何”进行重新分配,但内存复制肯定是将向量移动到新分配的内存空间的最快方法?
那么为什么当添加
std::vector
而不是内存复制时会调用复制/移动函数呢?
旁注/问题:(也许这应该是一个单独的问题):亲自移动c'tor为什么打印
moved fred -> fred.moved
而不是moved -> fred.moved
。看起来 std::string
移动分配并没有真正“移动”数据......
std::move
(xold.begin(), xold.end(), xnew.begin());
的东西。这取决于值类型,向量通常会进行自己的内部放置 new 。但如果它能动的话它就会动。
你的移动构造函数
person(const person &&other) noexcept;
但有一个缺陷:
other
不应该是const
,因为必须允许它更改other
以窃取其资源。在这个移动构造函数中
person(person&& other) noexcept : m_name(std::move(other.m_name)) {}
std::string
自己的移动构造函数将执行与此类似的操作:
string(string&& other) noexcept :
the_size(other.the_size),
data_ptr(std::exchange(other.data_ptr, nullptr))
{}
您还需要添加移动赋值运算符:
person& operator=(person &&other) noexcept;