为什么连续的vector :: push_back导致不同数量的构造函数调用?

时间:2016-12-15 10:26:12

标签: c++ vector destructor copy-constructor

class base
{
    private:
            int k;
    public:
            base(const base& b){ this->k = b.k; cout<<"  c-ctor "<<endl; }
            base(int a = 10){ k = a; }

            ~base(){cout << "destructor called\n";}
};

int main()
{
    base b, b1(2);
    vector<base> m;
    cout << "first pushback" <<endl;
    m.push_back(b);
    cout << "2nd pushback" <<endl;
    m.push_back(b1);
    cout << "3rd pushback" <<endl;
    m.push_back(b1);
    cout << "4th pushback" <<endl;
    m.push_back(b);
    cout << "5th pushback" <<endl;
    m.push_back(b);
    cout<<" =============================================== "<<endl;

    return 0;
}

输出:

first pushback
  c-ctor 
2nd pushback
  c-ctor 
  c-ctor 
destructor called
3rd pushback
  c-ctor 
  c-ctor 
  c-ctor 
destructor called
destructor called
4th pushback
  c-ctor    
5th pushback
  c-ctor 
  c-ctor 
  c-ctor 
  c-ctor 
  c-ctor 
destructor called
destructor called
destructor called
destructor called
 =============================================== 

destructor called
destructor called
destructor called
destructor called
destructor called
destructor called
destructor called

为什么 th push_back导致 i 数量的复制构造函数调用?

不是调整大小效果(即再次复制原始矢量)以及将元素插入矢量的效率低下的方法吗?

为什么 4 th push_back的行为与 2 th 不同, 3 th abd 5 th push_back

Demo

5 个答案:

答案 0 :(得分:9)

没什么大不了的。每当size到达capacity时,向量就会被重新分配。所有元素都从旧向量复制到新向量。

通常,为新矢量分配原始容量的两倍。

  1. 在第一个push_back之前,容量为1.因此不需要重新分配。
  2. 对于第二个push_back,容量需要加倍,因此会进行两次复制构造函数调用,首先将旧元素复制到新向量,然后再复制push_back。容量现在是2。
  3. 第三个push_back再次需要重新分配向量,因为容量现在为2.重新分配后容量变为4。
  4. 现在没有重新分配,所以只需要一次复制ctor(push_back)。容量仍然是4。
  5. 对于第5个push_back,重新分配发生,4个旧元素和一个新元素(push_back)被复制到新向量。容量现在是8。
  6. 如果你继续前进,你会发现重新分配将在9日push_back发生。

    此外,在重新分配时需要调用析构函数,此时不再需要旧的向量,因此应该销毁其中的成员。

答案 1 :(得分:4)

std::vector可以被视为动态数组。因此,当需要时它会增长,这需要向量分配更多内存, 复制 向量中的现有元素到新的更大内存。

然后破坏旧存储器中的现有对象。

如果您事先知道向量中需要多少元素,则可以reserve为向量存储。通过这样做,矢量不需要重新分配和复制数据,直到您达到新的容量。

我真的建议您详细了解std::vector,尤其是容量尺寸之间的区别。

答案 2 :(得分:2)

向量需要扩展以适应新元素。这需要将现有元素复制到新缓冲区中。

如果您的班级noexcept移动可构造,则会生成对移动构造函数的调用。

不是每个push_back导致向量重新分配内存的原因可能是由于您的标准库实现在调整大小后尝试保留一些额外的内存,以避免过于频繁地执行此操作。这样做是vector实施的自由,并且有几种策略可以确定预留多少。

答案 3 :(得分:1)

要退后一步,std::vector有一个关键要求:向量中的元素必须连续存储。这意味着您可以安全地使用指向向量中元素的指针作为指向该类型元素的原始指针,因此可以像使用固定大小的数组一样使用数组语义和指针算法。

这也意味着通过索引访问数组的任何元素非常快,并且无论数组的大小和您正在访问的特定元素的索引(这称为数组)都需要相同的时间。 O(1),或&#34;恒定时间&#34;操作)。

需要为此福利支付的罚款是重新分配。创建向量时,通常不知道要使用多少元素,因此分配一定量的连续内存来存储一些元素。如果继续添加向量,则将超过此初始分配。但是你不能只扩展向量使用的内存量,因为你的程序可能已经在当前向量之后立即将内存分配给其他变量。确保向量元素保持连续的唯一方法是分配一个足够大的新块来包含所有元素,包括附加元素,然后将所有元素复制到这个新位置。

正如其他人所指出的那样,每次超出容量时,通过分配原始数组大小的1.5或2倍,以指数方式增加数组大小是很常见的。这减少了整个数组在增长时需要重新分配的频率。

如果你想要一个元素集合,它总是相对快速地追加一个元素(或者至少,总是花费相同的时间)你可以使用链表(std::list),但是您无法再通过索引快速访问任何元素(&#34;随机访问&#34;)。

答案 4 :(得分:0)

  

“将元素插入向量的效率相当低的方式”

正如其他人所指出的那样,您可以在第一个实例中提示std::vector为您预留足够的内存,以便在必须重新分配时将项目推送到您的向量上达到此限制。如果您更改代码,则将以下调用添加到reserve

vector<base> m;
m.reserve(5);
cout << "first pushback" <<endl;

使用reserve不会更改向量的 size ,但会分配足够的内存来保存该大小的连续向量,从而避免重新分配。在您的示例中,您将看到正在调用的复制构造函数和析构函数的数量大幅减少。如果你知道(大致均匀)矢量的大小和类型的构造/破坏是昂贵的,这可能会对你的代码产生很大的影响。

请注意reserveresize非常不同 - 第一个在将项目推送到向量(您的示例)时最有用,第二个将创建完全填充的项目向量,运行复制构造函数N次;这在随后通过下标运算符访问和更改项时非常有用。