WeHack BBS
std::vector和复制构造函数的调用 - 可打印的版本

+- WeHack BBS (https://bbs.wehack.space)
+-- 版块: 计算机技术 (https://bbs.wehack.space/forum-5.html)
+--- 版块: 程序设计讨论区 (https://bbs.wehack.space/forum-14.html)
+--- 主题: std::vector和复制构造函数的调用 (/thread-179.html)



std::vector和复制构造函数的调用 - vimacs - 09-03-2020

代码:
#include <iostream>
#include <vector>

using namespace std;

class Base
{
    int i;
public:
    Base(int i_): i(i_) { cout << i << endl; }
    Base(const Base &b): i(b.i) { cout << "Copy " << i << endl; }
    ~Base() { cout << "Destruct " << i << endl; }
};

class VecBase
{
    vector<Base> v;
public:
    void push(int i) { v.push_back(Base(i)); }
    void emplace(int i) { v.emplace_back(i); }
};

int main()
{
    VecBase v;
    v.push(1);
    v.emplace(2);
}

为了观察std::vector中push_back和emplace_back的区别,我写了以上程序。根据输出发现,Base(1)被构造了3次,其中复制构造函数调了2次,Base(2)被构造了1次,没调用复制构造函数。那么Base(1)的复制构造函数被调了2次呢?

首先是 push_back 和 emplace_back 的区别,emplace_back是在 vector 中原地构造,所以没有复制的过程,而用 push_back 的时候,首先在 VecBase::push() 中用 Base(i) 构造了一次,然后在 std::vector<>::push_back() 中复制了一次。

那么还有一次复制构造函数来自哪里?注意到 std::vector 为了支持任意大小,在增加元素的时候会出现扩容操作,而扩容的方法是重新分配空间,然后把数据复制过去,因此会在复制的过程中调用复制构造函数。

为了验证这个想法,可以多往 std::vector 里面增加元素,就是在以上程序中多加 v.emplace(...).

代码:
int main()
{
    VecBase v;
    for (int i = 0; i <= 16; i++) {
        v.emplace(i);
    }
}

在这段程序中,可以发现当 i>0 时,当 i 达到 2^n (n=0,1,2,3,4) 的时候,调用完 Base(i) 构造函数之后,就会对所有小于i的对象逐个执行复制构造函数,然后逐个执行析构函数。这同时可以看出,libstdc++对vector的扩容方式是在放不下数据的时候,对空间进行倍增。

总结一下,如果不想对存在 vector 里面的对象调用太多复制构造函数,就不要经常增大 vector 的容量,否则 vector 容量倍增的时候会对其中的所有对象调复制构造函数。可以先用 std::vector<>::reserve() 预留空间,再做 emplace_back 操作。其他解决方案包括在 vector 中存放指针,这样在容量倍增的时候只复制指针而不是对象本身。

此外,在考虑 move 构造函数的时候,可以发现
代码:
v.push_back(Base(i));
调的是类 Base 的 move 构造函数,因此和
代码:
Base t(i); v.push_back(t)
不同。