std::vector copy/move 元素在调整大小时?

does std::vector copy/move elements when re-sizing?

我为了学习/刷新练习而与 move c'tors 打交道,我遇到了一些出乎我意料的事情。下面我有一个 class person,其中包含一个 std::string m_name;。我用这个作为 class 对 copy/move 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 在必须重新分配 space 时“移动”了。但是我认为它做的是记忆copy/move,而不是逐个对象copy/move。真没想到会调用move构造函数

现在如果我删除 move c'tor,我发现 bob 被复制了三次!:https://godbolt.org/z/_BxnvU 这看起来真的很低效。

来自 cplusplus.com:

push_back()

Add element at the end Adds a new element at the end of the vector, after its current last element. The content of val is copied (or moved) to the new element.

This effectively increases the container size by one, which causes an automatic reallocation of the allocated storage space if -and only if- the new vector size surpasses the current vector capacity.

调整大小()

Resizes the container so that it contains n elements.

If n is smaller than the current container size, the content is reduced to its first n elements, removing those beyond (and destroying them).

If n is greater than the current container size, the content is expanded by inserting at the end as many elements as needed to reach a size of n. If val is specified, the new elements are initialized as copies of val, otherwise, they are value-initialized.

If n is also greater than the current container capacity, an automatic reallocation of the allocated storage space takes place.

Notice that this function changes the actual content of the container by inserting or erasing elements from it.

我猜它并没有真正描述它“如何”进行重新分配,但内存复制肯定是将向量移动到其新分配的内存的最快方法 space?

那么为什么在添加 std::vector 而不是内存副本时调用 copy/move c'tor?

一方note/question:(也许这应该是一个单独的问题):亲自移动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;