为什么c ++用零来初始化std :: vector,而不是std :: array?

时间:2018-02-13 20:54:30

标签: c++ vector

当你不想要它时,用零来初始化一个向量不是浪费时间吗?

我试试这段代码:

#include <iostream>
#include <vector>
#include <array>

#define SIZE 10

int main()
{
#ifdef VECTOR

  std::vector<unsigned> arr(SIZE);

#else

  std::array<unsigned, SIZE> arr;

#endif // VECTOR

  for (unsigned n : arr)
    printf("%i ", n);
  printf("\n");

  return 0;
}

我得到了输出:

带矢量

$ g++ -std=c++11 -D VECTOR test.cpp -o test && ./test 
0 0 0 0 0 0 0 0 0 0 

带数组

g++ -std=c++11  test.cpp -o test && ./test 
-129655920 32766 4196167 0 2 0 4196349 0 1136 0 

我也尝试使用clang ++

那么为什么零呢?顺便说一下,我可以在不初始化的情况下声明一个向量吗?

3 个答案:

答案 0 :(得分:4)

声明向量的更常见方法是不指定大小:

std::vector<unsigned> arr;

这不会为向量内容分配任何空间,也没有任何初始化开销。元素通常使用.push_back()等方法动态添加。如果要分配内存,可以使用reserve()

arr.reserve(SIZE);

这不会初始化添加的元素,它们不包含在向量的size()中,并且尝试读取它们是未定义的行为。将此与

进行比较
arr.resize(SIZE);

生长向量并初始化所有新元素。

另一方面,

std::array总是分配内存。它实现了与C风格数组相同的大多数行为,除了指针的自动衰减。这包括默认情况下不初始化元素。

答案 1 :(得分:2)

默认分配器正在进行零初始化。您可以使用不这样做的其他分配器。我写了一个分配器,它在可行时使用默认构造而不是初始化。更准确地说,它是一个名为ctor_allocator的分配器包装器。然后我定义了一个vector模板。

dj:vector<unsigned> vec(10);完全符合您的要求。它是std::vector<unsigned> (10),未初始化为零。

--- libdj/vector.h ----
#include <libdj/allocator.h>
#include <vector>

namespace dj {
template<class T>
    using vector = std::vector<T, dj::ctor_allocator<T>>;
}

--- libdj/allocator.h  ----
#include <memory>

namespace dj {

template <typename T, typename A = std::allocator<T>>
    class ctor_allocator : public A 
    {
        using a_t = std::allocator_traits<A>;
    public:
        using A::A; // Inherit constructors from A

        template <typename U> struct rebind 
        {
            using other =
                ctor_allocator
                <  U, typename a_t::template rebind_alloc<U>  >;
        };

        template <typename U>
        void construct(U* ptr)
            noexcept(std::is_nothrow_default_constructible<U>::value) 
        {
            ::new(static_cast<void*>(ptr)) U;
        }

        template <typename U, typename...Args>
        void construct(U* ptr, Args&&... args) 
        {
            a_t::construct(static_cast<A&>(*this),
                ptr, std::forward<Args>(args)...);
        }
    };
}

答案 2 :(得分:1)

假设我们有一些课程:

class MyClass {
    int value;

public:
    MyClass() {
        value = 42;
    }
    // other code
};

std::vector<MyClass> arr(10);将默认构建10个MyClass副本,所有副本均为value = 42

但是假设它没有默认构建10个副本。现在,如果我写了arr[0].some_function(),那就出现了问题:MyClass的构造函数尚未运行,因此不会设置类的不变量。我可能假设在some_function() value == 42的实现中,但由于构造函数没有运行,value有一些不确定的值。这将是一个错误。

这就是为什么在C ++中有object lifetimes的概念。在调用构造函数之前,该对象不存在,并且在调用析构函数后它不再存在。 std::vector<MyClass> arr(10);调用每个元素的默认构造函数,以便所有对象都存在。

值得注意的是std::array有些特殊,因为它是initialized following the rules of aggregate initialization。这意味着std::array<MyClass, 10> arr;也默认使用MyClass构建10个value = 42个副本。但是,对于unsigned等非类类型,值将是不确定的。

有一种方法可以避免调用所有默认构造函数:std::vector::reserve。如果我写的话:

std::vector<MyClass> arr;
arr.reserve(10);

向量将分配其后备数组以保存10 MyClass s,并且它不会调用默认构造函数。但现在我无法写arr[0]arr[5];那些将是arr的越界访问(arr.size()仍为0,即使后备数组有更多元素)。要初始化值,我必须致电push_backemplace_back

arr.push_back(MyClass{});

这通常是正确的方法。例如,如果我想使用arr中的随机值填充std::rand,我可以使用std::generate_nstd::back_inserter

std::vector<unsigned> arr;
arr.reserve(10);
std::generate_n(std::back_inserter(arr), 10, std::rand);

还值得注意的是,如果我已经在容器中拥有了arr所需的值,我可以使用构造函数传递begin()/end()

std::vector<unsigned> arr{values.begin(), values.end()};