C++ 重新调整大小时,std::vector是否复制/移动元素?

C++ 重新调整大小时,std::vector是否复制/移动元素?,c++,c++11,vector,move-semantics,C++,C++11,Vector,Move Semantics,我在和搬家者混日子,做一个学习/刷新练习,我遇到了一些意想不到的事情。下面我有一个类person,它包含一个std::string m_name;。我将此用作复制/移动任务的测试类 以下是供快速参考的代码: #include <iostream> #include <vector> class person { public: std::string m_name; explicit person(const std::string &name)

我在和搬家者混日子,做一个学习/刷新练习,我遇到了一些意想不到的事情。下面我有一个类person,它包含一个std::string m_name;。我将此用作复制/移动任务的测试类

以下是供快速参考的代码:

#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内容没有移动之外:

然后删除std::vector保留,以便在添加元素时std::vector必须增长。现在我得到了一些我真的没有想到的东西:

现在我可以看到,当添加fred时,bob被复制,然后被移动,当添加joe时,bob又被移动。我的印象是std::vector在需要重新分配空间时会移动。但我认为它是一个内存复制/移动,而不是一个对象一个对象的复制/移动。我真的没想到它会调用move构造函数

现在如果我删除移动命令,我发现bob被复制了三次!: 这似乎效率很低

从cplusplus.com:

推回

末尾添加元素在向量末尾添加新元素, 在其当前的最后一个元素之后。val的内容被复制或删除 移到新元素

这将有效地将容器大小增加1,从而导致 自动重新分配分配的存储空间(如果且仅当- 新矢量大小超过当前矢量容量

调整大小

调整容器大小,使其包含n个元素

如果n小于当前容器大小,则内容为 减少到它的前n个元素,移除超过的元素并摧毁 他们

如果n大于当前容器大小,则内容为 通过在末尾插入尽可能多的元素来扩展,以达到 n的大小。如果指定了val,则新元素将初始化为 val的副本,否则,将对其进行值初始化

如果n也大于当前集装箱容量,则自动 重新分配分配的存储空间

请注意,此函数会更改容器的实际内容 通过插入或删除其中的元素

我想它并没有真正描述它是如何进行重新分配的,但是内存拷贝肯定是将向量移动到其新分配的内存空间的最快方式吗

那么,当添加std::vector而不是内存拷贝时,为什么要调用copy/move命令呢


旁注/问题:任何可能这应该是一个单独的问题:面对面移动c'tor为什么移动fred->fred.moved printed and not moved->fred.moved。std::string move赋值似乎没有真正移动数据…

如果需要重新定位,类似于xold.begin、xold.end、xnew.begin;将使用。它取决于值类型,并且向量通常会自己进行内部放置。但如果它能移动,它就会移动

你的移动构造器

person(const person &&other) noexcept;
person(person&& other) noexcept : m_name(std::move(other.m_name)) {}
但有一个缺陷:other不应该是const,因为必须允许它更改other以窃取其资源。在此移动构造函数中

person(const person &&other) noexcept;
person(person&& other) noexcept : m_name(std::move(other.m_name)) {}
std::strings自身的移动构造函数将执行类似的操作:

string(string&& other) noexcept : 
    the_size(other.the_size),
    data_ptr(std::exchange(other.data_ptr, nullptr))
{}
您还需要添加移动分配操作符:

person& operator=(person &&other) noexcept;

如果需要重新定位,可以使用类似于xold.begin、xold.end、xnew.begin的东西;将使用。它取决于值类型,并且向量通常会自己进行内部放置。但如果它能移动,它就会移动

你的移动构造器

person(const person &&other) noexcept;
person(person&& other) noexcept : m_name(std::move(other.m_name)) {}
但有一个缺陷:other不应该是const,因为必须允许它更改other以窃取其资源。在此移动构造函数中

person(const person &&other) noexcept;
person(person&& other) noexcept : m_name(std::move(other.m_name)) {}
std::strings自身的移动构造函数将执行类似的操作:

string(string&& other) noexcept : 
    the_size(other.the_size),
    data_ptr(std::exchange(other.data_ptr, nullptr))
{}
您还需要添加移动分配操作符:

person& operator=(person &&other) noexcept;

但是,内存拷贝肯定是将向量移动到其新分配的内存空间的最快方式吗?你不能仅仅是McCPy C++对象。如果它们包含指向自身的指针呢?这是一个很好的观点,这意味着分配给向量可能比我想的更糟特别是如果您创建的类没有move c'tors:o。如果可以的话,我想一定要保留一些空间:这个问题可能很快就会得到解决:但是内存拷贝肯定是将向量移动到其新分配的内存空间的最快方法吗?你不能仅仅是McCPy C++对象。如果它们包含指向自身的指针呢?这是一个很好的观点,这意味着分配给向量可能比我想的更糟特别是如果您创建的类没有move c'tors:o。如果可以的话,我想一定要预留一些空间:这个问题可能很快就会得到解决:啊,谢谢,现在对我来说有意义了:。我添加移动任务并不是为了尽可能简单。继续移动c'tor-已经是const了,还是我遗漏了什么?@code\u fodder Great!:我在回答中又补充了一点关于非常量的必要性。我看错了你的常量评论。我从移动任务中删除了const我以为你说需要添加co
nst,现在我从fred:moved->fred.moved得到了正确的输出,之前它是:moved fred->fred.moved,这意味着它实际上没有移动-这解决了这个小小的误解-谢谢@代码饲料哦,我错过了你误读它:-很好,它成功了。不客气!啊,谢谢,现在对我来说有意义了。我添加移动任务并不是为了尽可能简单。继续移动c'tor-已经是const了,还是我遗漏了什么?@code\u fodder Great!:我在回答中又补充了一点关于非常量的必要性。我看错了你的常量评论。我把const从move c'tor中删除了,我想你说需要添加const,现在我从fred:moved->fred.moved中得到了正确的输出,之前它是:moved fred->fred.moved,意思是它实际上没有移动-这解决了这个小误解-谢谢@代码饲料哦,我错过了你误读它:-很好,它成功了。不客气!