矢量:初始化还是保留?

时间:2012-01-19 15:19:47

标签: c++ vector

我知道向量的大小,这是初始化它的最佳过程吗?:

选项1

vector<int> vec(3); //in .h
vec.at(0)=var1;     //in .cpp
vec.at(1)=var2;     //in .cpp
vec.at(2)=var3;     //in .cpp

选项2

vector<int> vec;     //in .h
vec.reserve(3);     //in .cpp
vec.push_back(var1);     //in .cpp
vec.push_back(var2);     //in .cpp
vec.push_back(var3);     //in .cpp

我猜选项2优于1.是吗?其他选择?

9 个答案:

答案 0 :(得分:41)

两种变体都有不同的语义,即你要比较苹果和橘子。

第一个给出了n个默认初始化值的向量,第二个变量保留了内存,但没有初始化它们。

选择更适合您需求的内容,即在某种情况下“更好”的内容。

答案 1 :(得分:40)

“最佳”方式是:

vector<int> vec = {var1, var2, var3};

可用于支持C ++ 11的编译器。

通过标题或实现文件中的操作不确定您的意思。一个可变的全球对我来说是禁忌。如果它是类成员,则可以在构造函数初始化列表中初始化它。

否则,如果你知道要使用多少项,那么通常会使用选项1,默认值(int为0)会很有用。
在此处使用at意味着您无法保证索引有效。像这样的情况令人担忧。即使您能够可靠地检测到问题,使用push_back也更加简单,并且不再担心索引是否正确。

在选项2的情况下,无论您是否保留内存,通常它都会产生零性能差异,因此更容易保留*。除非矢量包含复制成本非常高的类型(并且不能在C ++ 11中提供快速移动),否则矢量的大小将会非常大。


*来自Stroustrups C++ Style and Technique FAQ

  

人们有时会担心std :: vector增长的成本   增量。我曾经担心这一点,并使用reserve()来   优化增长。在测量我的代码并反复拥有之后   无法在实际中找到reserve()的性能优势   程序,我停止使用它,除非需要避免   迭代器失效(在我的代码中很少见)。再次:先测量   你优化。

答案 2 :(得分:8)

虽然您的示例基本相同,但可能是当使用的类型不是int时,您可以选择。如果您的类型没有默认构造函数,或者您以后必须重新构造每个元素,我会使用reserve。只是不要陷入我所做的陷阱,然后使用reserve然后operator[]进行初始化!

构造

std::vector<MyType> myVec(numberOfElementsToStart);
int size = myVec.size();
int capacity = myVec.capacity();

在第一种情况下,使用构造函数,sizenumberOfElementsToStart将相等,capacity将大于或等于它们。

将myVec视为包含许多MyType项的向量,可以访问和修改它,push_back(anotherInstanceOfMyType)会将它附加到向量的末尾。

预订

std::vector<MyType> myVec;
myVec.reserve(numberOfElementsToStart);
int size = myVec.size();
int capacity = myVec.capacity();

使用reserve函数时,size将为0,直到您向数组添加元素,capacity将等于或大于numberOfElementsToStart }。

将myVec视为一个向量,可以使用push_back 添加新项目,而不使用内存分配至少第一个{{1元素。

请注意,numberOfElementsToStart仍需要进行内部检查,以确保 size&lt;容量增量大小,因此您可能需要将其与默认构建的成本进行权衡。

列表初始化

push_back()

这是初始化矢量的附加选项,虽然它仅适用于非常小的矢量,但它是一种清晰的方法来初始化具有已知值的小矢量。 std::vector<MyType> myVec{ var1, var2, var3 }; 将等于您初始化它的元素数量,size将等于或大于size。现代编译器可以优化临时对象的创建并防止不必要的复制。

答案 3 :(得分:6)

选项2更好,因为reserve只需要保留内存(3 * sizeof(T)),而第一个选项为容器内的每个单元调用基类型的构造函数。

对于类似C的类型,它可能是相同的。

答案 4 :(得分:2)

不知何故,一个完全错误的非答案性答案一直被接受,并且在大约7年中被人们最强烈地评价。这不是一个苹果和桔子的问题。这不是用模糊的陈词滥调来回答的问题。

要遵循的简单规则:

选项1更快... enter image description here enter image description here

...但是,这可能不是您最关心的问题。

首先,区别很小。其次,随着我们加快编译器优化的步伐,两者之间的差异变得越来越小。例如,在我的gcc-5.4.0上,运行3级编译器优化(-O3)时,差异可以说是微不足道的: enter image description here

因此,通常,我会建议您在遇到这种情况时使用方法1。但是,如果您不记得哪个是最佳选择,那么可能就不值得花时间去寻找。只需选择一个然后继续,因为这不可能导致整个程序的速度明显下降。


通过从正态分布中采样随机向量大小,然后使用两种方法对这些大小的向量进行初始化计时,来运行这些测试。我们保留一个虚拟的sum变量,以确保不优化向量初始化,并随机化向量大小和值,以努力避免由于分支预测,缓存和其他此类技巧而导致的任何错误。

main.cpp

/* 
 * Test constructing and filling a vector in two ways: construction with size
 * then assignment versus construction of empty vector followed by push_back
 * We collect dummy sums to prevent the compiler from optimizing out computation
 */

#include <iostream>
#include <vector>

#include "rng.hpp"
#include "timer.hpp"

const size_t kMinSize = 1000;
const size_t kMaxSize = 100000;
const double kSizeIncrementFactor = 1.2;
const int kNumVecs = 10000;

int main() {
  for (size_t mean_size = kMinSize; mean_size <= kMaxSize;
       mean_size = static_cast<size_t>(mean_size * kSizeIncrementFactor)) {
    // Generate sizes from normal distribution
    std::vector<size_t> sizes_vec;
    NormalIntRng<size_t> sizes_rng(mean_size, mean_size / 10.0); 
    for (int i = 0; i < kNumVecs; ++i) {
      sizes_vec.push_back(sizes_rng.GenerateValue());
    }
    Timer timer;
    UniformIntRng<int> values_rng(0, 5);
    // Method 1: construct with size, then assign
    timer.Reset();
    int method_1_sum = 0;
    for (size_t num_els : sizes_vec) {
      std::vector<int> vec(num_els);
      for (size_t i = 0; i < num_els; ++i) {
        vec[i] = values_rng.GenerateValue();
      }
      // Compute sum - this part identical for two methods
      for (size_t i = 0; i < num_els; ++i) {
        method_1_sum += vec[i];
      }
    }
    double method_1_seconds = timer.GetSeconds();
    // Method 2: reserve then push_back
    timer.Reset();
    int method_2_sum = 0;
    for (size_t num_els : sizes_vec) {
      std::vector<int> vec;
      vec.reserve(num_els);
      for (size_t i = 0; i < num_els; ++i) {
        vec.push_back(values_rng.GenerateValue());
      }
      // Compute sum - this part identical for two methods
      for (size_t i = 0; i < num_els; ++i) {
        method_2_sum += vec[i];
      }
    }
    double method_2_seconds = timer.GetSeconds();
    // Report results as mean_size, method_1_seconds, method_2_seconds
    std::cout << mean_size << ", " << method_1_seconds << ", " << method_2_seconds;
    // Do something with the dummy sums that cannot be optimized out
    std::cout << ((method_1_sum > method_2_sum) ? "" : " ") << std::endl;
  }

  return 0;
}

我使用的头文件位于这里:

答案 5 :(得分:1)

从长远来看,它取决于元素的用法和数量。

运行以下程序以了解编译器如何保留空间:

vector<int> vec;
for(int i=0; i<50; i++)
{
  cout << "size=" << vec.size()  << "capacity=" << vec.capacity() << endl;
  vec.push_back(i);
}

size是实际元素的数量,capacity是数组到imlement vector的实际大小。 在我的电脑中,直到10,两者都是一样的。但是,当大小为43时,容量为63.根据元素的数量,可能更好。例如,增加容量可能很昂贵。

答案 6 :(得分:1)

另一个选择是信任您的编译器(tm)并执行push_back而不先调用reserve。当你开始添加元素时,它必须分配一些空间。也许它和你一样好吗?

它更好&#34;有更简单的代码来完成同样的工作。

答案 7 :(得分:1)

工作原理

这是特定于实现的,但是一般情况下,Vector数据结构内部将具有指向元素实际驻留的内存块的指针。默认情况下,GCC和VC ++都分配0个元素。因此,默认情况下,您可以将Vector的内部存储器指针视为nullptr

当您在选项1中调用vector<int> vec(N);时,将使用默认构造函数创建N个对象。这称为 fill构造函数

当你在选项2中的默认构造函数之后执行vec.reserve(N); 时,你得到的数据块可以容纳3个元素但是没有创建任何对象,这与选项1不同。

为何选择选项1

如果您知道矢量将保持的元素数量,并且您可能将大多数元素保留为其默认值,那么您可能希望使用此选项。

为何选择选项2

这个选项通常比两者更好,因为它只为将来使用分配数据块而不是实际填充从默认构造函数创建的对象。

答案 8 :(得分:0)

由于它似乎已经过去了5年,而且错误的答案仍然是被接受的答案,并且最受欢迎的答案完全无用(错过森林的树木),我将添加一个真实的答案。

方法#1 :我们将初始大小参数传递给向量(让我们称之为n。这意味着向量中填充了n元素,这将是初始化为默认值。例如,如果向量包含int s,则将填充n个零。

方法#2 :我们首先创建一个空向量。然后我们为n元素保留空间。在这种情况下,我们从不创建n元素,因此我们永远不会对向量中的元素执行任何初始化。由于我们计划立即覆盖每个元素的值,因此缺少初始化对我们没有任何害处。另一方面,由于我们总体上做得较少,这将是更好的选择。

* 更好 - 真正的定义:永远不会更糟。智能编译器总是可以找出你想要做的事情并为你优化它。

结论:使用方法#2。