【问题标题】:How does dynamic arrays(std::vector) work in c++?动态数组(std::vector)如何在 C++ 中工作?
【发布时间】:2021-11-20 23:51:40
【问题描述】:

据我所知,std::vector 连续存储数据 当您尝试添加或删除一个元素时,它会通过分配新内存来增长或缩小,并将所有内容从旧内存复制到新内存中并删除旧内存

在下面的代码中,我有一个复制构造函数 它说每次复制课程时都复制 当我有 3 个元素时,它给了我 6 条复制的消息,这很好 但是当我添加另一个元素时,它给了我 7 条消息

不是应该给我10个吗? 以及如何优化它?

#include <iostream>
#include <vector>

struct Vertex
{
    int x, y, z;

    Vertex(int x, int y, int z) : x(x), y(y), z(z)
    {
    }

    Vertex(const Vertex &vetrex) : x(vetrex.x), y(vetrex.y), z(vetrex.z)
    {
        std::cout << "Copied" << std::endl;
    }
};

int main()
{
    std::vector<Vertex> vertices;
    vertices.push_back({1, 2, 3});
    vertices.push_back({4, 5, 6});
    vertices.push_back({7, 8, 9});
    vertices.push_back({10, 11, 12});
}

【问题讨论】:

  • 你能更详细地解释一下为什么你认为它应该是 10 吗?
  • @NathanPierson 10 = 1+2+3+4。看不到 10 的原因是向量使用几何增长而不是线性增长。
  • 首先将第一个元素从主乐趣堆栈复制到向量堆栈的第 2 和第 3 个,因为它将第一个元素和第 2 个元素复制到新的分配第 4、第 5 和第 6 个因为它将做同样的事情,但对于 3 个元素,然后它应该将 4 个元素处理到新的内存中
  • @SobhyRzk 好的,是的,如果向量的容量为N 并添加了一个新元素,它只会为N+1 元素保留空间。但正如@RaymondChen 指出的那样,容量增长不仅仅是线性的,所以实际上它根本不需要为第四个push_back 重新分配和复制。 (确切的重新分配策略取决于实现。)
  • vector 将使用实施者想到的每一个技巧,并且可以证明有效减少所需的复制量。有时你会看到它做一些鬼鬼祟祟的事情,比如根本没有副本,因为它以一种或另一种方式注意到它已经拥有所需的所有存储空间。

标签: c++ c++11 visual-c++ c++17 c++14


【解决方案1】:

让我们一步一步来:

std::vector<Vertex> vertices;

此时容量为0,大小为0。

vertices.push_back({1, 2, 3});

Vector 分配一个大小为 1 的缓冲区来保存元素。 将传递给 push_back 的构造参数复制到向量 [1/7 副本]

vertices.push_back({4, 5, 6});

向量容量为 1,大小为 1。它已满,因此要添加第二个元素,它会分配一个新的缓冲区,其大小为两倍(现在为 2)。

  • 将向量中的现有元素复制到新内存中 [2/7 副本]
  • 将传递给 push_back 的构造参数复制到向量 [3/7 副本]
  • 删除旧缓冲区(并销毁其中的对象)

然后:

vertices.push_back({7, 8, 9});

向量容量为 2,大小为 2。它再次满了,并通过分配大小为 4 的缓冲区使其缓冲区大小加倍。

  • 将 2 个元素从旧分配复制到新分配 [(4&5)/7 份]
  • 将传递给 push_back 的参数复制到向量 [6/7] 副本中
  • 删除旧缓冲区(并销毁其中的对象)

然后:

vertices.push_back({10, 11, 12});

向量容量为 4,大小为 3。它有空间容纳新元素而无需调整大小。

  • 将传递给 push_back 的参数复制到向量 [7/7] 副本中

注意,向量“加倍”很常见,但标准并不要求。实现可以调整到其他数量,因此不要依赖于预期的重新分配大小。

此外,为了减少重新分配的次数,您可以在所有插入之前保留大小。在这种情况下,它的效率要高得多:

std::vector<Vertex> vertices;
vertices.reserve(4);

如果您在开头添加为 4 个元素保留空间的调用,则只有 4 个副本 - 每个元素一个添加到向量中。

【讨论】:

    【解决方案2】:

    根据我听说std::vector 连续存储数据,当您尝试添加或删除一个元素时,它会通过分配新内存来增长或缩小,并将旧内存中的所有内容复制到新内存中,并删除旧记忆

    std::vector&lt;T&gt; 实际上并不会在您每次向其添加元素时增长。它有一个叫做容量的概念(参见std::vector&lt;T&gt;::capacity())。

    每次添加元素时实际增加或减少的数字是std::vector&lt;T&gt;::size(),这只是容量的限制边界。容量实际上告诉您当时已分配了多少元素。

    基本上,在大多数实现中,std::vector&lt;T&gt; 的容量会以 2 的幂次方增长。(这样做是有原因的,这主要与性能有关。)

    因此,请检查每个 push_back() 调用中的容量以查看以下内容:

    int main()
    {
        std::vector<Vertex> vertices;
        std::cout << "Size: " << vertices.size() << std::endl;         // Size: 0
        std::cout << "Capacity: " << vertices.capacity() << std::endl; // Capacity: 0
        vertices.push_back({1, 2, 3});                                 // 1 copy 
        std::cout << "***" << std::endl;
    
        std::cout << "Size: " << vertices.size() << std::endl;         // Size: 1
        std::cout << "Capacity: " << vertices.capacity() << std::endl; // Capacity: 2^0 = 1
        vertices.push_back({4, 5, 6});                                 // 2 copies (due to reallocation)
        std::cout << "***" << std::endl;
    
        std::cout << "Size: " << vertices.size() << std::endl;         // Size: 2
        std::cout << "Capacity: " << vertices.capacity() << std::endl; // Capacity: 2^1 = 2
        vertices.push_back({7, 8, 9});                                 // 3 copies (due to reallocation)
        std::cout << "***" << std::endl;
    
        std::cout << "Size: " << vertices.size() << std::endl;         // Size: 3
        std::cout << "Capacity: " << vertices.capacity() << std::endl; // Capacity: 2^2 = 4
        vertices.push_back({10, 11, 12});                              // 1 copy
        std::cout << "***" << std::endl;
    }
    

    如您所见,在第四次调用push_back()时,vertices的容量为4,其大小为3,因此方便地只复制一份,无需重新分配任何东西,因为已经有足够的空间。

    以及如何优化它?

    如果您事先已经知道要添加多少元素,您可以在调用任何push_back() 之前调用std::vector&lt;T&gt;::reserve()

    vertices.reserve(4);
    

    这避免了任何形式的重新分配,并且只复制了 4 份。

    【讨论】:

      猜你喜欢
      • 2011-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-31
      • 2010-10-14
      • 1970-01-01
      相关资源
      最近更新 更多