繁体   English   中英

动态数组(std::vector)如何在 c++ 中工作?

[英]How does dynamic arrays(std::vector) work in c++?

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

在下面的代码中,我有一个复制构造函数,它说每次复制 class 时都会复制它,当我有 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});
}

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

std::vector<T>实际上并不是每次向其添加元素时都增长。 它有一个叫做容量的概念(参见std::vector<T>::capacity() )。

每次添加元素时增长或收缩的实际数字是std::vector<T>::size() ,它只是容量的限制边界。 容量实际上告诉你当时分配了多少元素。

本质上,在大多数实现中, std::vector<T>的容量以 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<T>::reserve()

vertices.reserve(4);

这避免了任何类型的重新分配,只需复制 4 份。

让我们逐步了解它:

std::vector<Vertex> vertices;

此时,capacity为0,size为0。

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

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

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

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

  • 将 vector 中的现有元素复制到新的 memory [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 个副本——每个添加到向量中的元素一个。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM