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


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 calledn";}
};
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

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

不是调整大小效果(即再次复制原始向量),而是将元素插入向量的效率低下的方式?

为什么 4 Th push_back的行为与 2 th 3 th abd 5 th push_back

demo

没什么大不了的。每次size达到其capacity时,都将向量重新分配。所有元素均从旧向量复制到新向量。

通常,将原始容量分配给新向量的两倍。

  1. 在第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。

如果您进一步继续,您会观察到重新分配将发生在9th push_back

此外,在重新分配时需要调用攻击器,而当不再需要较旧的向量并应销毁其成员时。

std::vector可以看作是动态数组。因此,它在需要时会生长,这需要向量分配更多内存, copy 向量中的现有元素与新的较大内存。

然后破坏了旧内存中的现有对象。

如果您事先知道向量中需要多少个元素,则可以保留存储器的内存。通过执行此操作,向量无需重新分配和复制数据,直到您达到新容量为止。

我真的建议您阅读有关std::vector的更多信息,尤其是有关其及其 size 的差异。ANS">

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

如果您的班级是noexcept移动可构造,则它将生成对移动构造函数的呼叫。

并非每个push_back都会导致矢量重新定位内存的原因,这可能是由于您的标准库实现试图在调整大小后保留一些额外的内存,以免经常进行操作。这是在vector实施的自由度之内,并且有几种策略来确定要保留多少。

退后一步是STD ::向量的关键要求:向量中的元素必须连续存储。这意味着您可以安全地将向量中元素的指针用作指向该类型元素的原始指针,因此可以按照固定尺寸的数组来使用数组语义和指针算术。

这也意味着通过其索引访问数组的任何元素非常快,并且无论数组的大小如何,都需要相同的时间,以及您要访问的特定元素的索引(这称为一个称为o(1)或"恒定时间"操作)。

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

正如其他人所指出的那样,每次超过容量时,通过分配1.5或2倍的原始阵列大小的1.5或2倍,将数组尺寸成倍增加。这减少了整个数组在生长时需要重新分配的方式。

相反,如果您想要一个元素的集合,其中它总是相对较快地添加元素(或者至少始终需要相同的时间),则可以使用链接列表(std :: list),但然后您不能再通过索引快速访问任何元素("随机访问")。

"将元素插入向量的效率低下"

正如其他人指出的那样,您可以提示std::vector在首先为您保留足够的内存,以允许将项目推到您的矢量上,直至此限制,必须重新分配。如果更改代码将以下调用添加到reserve

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

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

请注意,reserveresize有很大不同 - 当您将项目推到矢量(您的示例)时,第一个最有用的是,第二个将创建一个填充的项目,并运行复制构造函数n次;随后通过下标运算符访问和更改项目时,这很有用。

最新更新