简体   繁体   English

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

[英]Why does c++ initialise a std::vector with zeros, but not a std::array?

Isn't it a waste of time to initialize a vector with zeros, when you don't want it? 当你不想要它时,用零来初始化矢量不是浪费时间吗?

I try this code: 我试试这段代码:

#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;
}

and I get the output: 我得到了输出:

with vector 与矢量

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

with an array 用数组

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

And I also try with clang++ 我也尝试使用clang ++

So why zeros? 为什么零呢? And by the way, could I declare a vector without initializing it? 顺便说一句,我可以在不初始化的情况下声明一个向量吗?

The more common way to declare a vector is without specifying the size: 声明向量的更常见方法是不指定大小:

std::vector<unsigned> arr;

This doesn't allocate any space for the vector contents, and doesn't have any initialization overhead. 这不会为向量内容分配任何空间,也没有任何初始化开销。 Elements are usually added dynamically with methods like .push_back() . 元素通常使用.push_back()等方法动态添加。 If you want to allocate memory you can use reserve() : 如果要分配内存,可以使用reserve()

arr.reserve(SIZE);

This doesn't initialize the added elements, they're not included in the size() of the vector, and trying to read them is undefined behavior. 这不会初始化添加的元素,它们不包含在向量的size()中,并且尝试读取它们是未定义的行为。 Compare this with 比较这个

arr.resize(SIZE);

which grows the vector and initializes all the new elements. 它会增长向量并初始化所有新元素。

std::array , on the other hand, always allocates the memory. 另一方面, std::array总是分配内存。 It implements most of the same behaviors as C-style arrays, except for the automatic decay to a pointer. 它实现了与C风格数组相同的大多数行为,除了指针的自动衰减。 This includes not initializing the elements by default. 这包括默认情况下不初始化元素。

The default allocator is doing the zero-initialization. 默认分配器正在进行零初始化。 You can use a different allocator that does not do that. 您可以使用不这样做的其他分配器。 I wrote an allocator that uses default construction rather than initialization when feasible. 我写了一个分配器,它在可行时使用默认构造而不是初始化。 More precisely, it is an allocator-wrapper called ctor_allocator . 更确切地说,它是一个名为ctor_allocator的分配器包装器。 Then I define a vector template. 然后我定义了一个vector模板。

dj:vector<unsigned> vec(10); does exactly what you want. 完全符合你的要求。 It's an std::vector<unsigned> (10) that is not initialized to zeros. 这是一个未初始化为零的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)...);
        }
    };
}

Suppose we have some class: 假设我们有一些课程:

class MyClass {
    int value;

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

std::vector<MyClass> arr(10); will default construct 10 copies of MyClass , all with value = 42 . 将默认构造10个MyClass副本,全部value = 42

But suppose it didn't default construct the 10 copies. 但是假设它没有默认构建10个副本。 Now if I wrote arr[0].some_function() , there's a problem: MyClass 's constructor has not yet run, so the invariants of the class aren't set up. 现在如果我写了arr[0].some_function() ,就会出现问题: MyClass的构造函数还没有运行,所以没有设置类的不变量。 I might have assumed in the implementation of some_function() that value == 42 , but since the constructor hasn't run, value has some indeterminate value. 我可能在some_function()的实现中假设value == 42 ,但由于构造函数没有运行,因此value具有一些不确定的值。 This would be a bug. 这将是一个错误。

That's why in C++, there's a concept of object lifetimes . 这就是为什么在C ++中,有一个对象生命周期的概念。 The object doesn't exist before the constructor is called, and it ceases to exist after the destructor is called. 在调用构造函数之前,该对象不存在,并且在调用析构函数后它不再存在。 std::vector<MyClass> arr(10); calls the default constructor on every element so that all the objects exist. 调用每个元素的默认构造函数,以便存在所有对象。

It's important to note that std::array is somewhat special, since it is initialized following the rules of aggregate initialization . 重要的是要注意std::array有点特殊,因为它是按照聚合初始化规则初始化的 This means that std::array<MyClass, 10> arr; 这意味着std::array<MyClass, 10> arr; also default constructs 10 copies of MyClass all with value = 42 . 默认情况下,还会构造10个MyClass副本,其value = 42 However, for non-class types such as unsigned , the values will be indeterminate. 但是,对于非类型类型(如unsigned ,值将是不确定的。


There is a way to avoid calling all the default constructors: std::vector::reserve . 有一种方法可以避免调用所有默认构造函数: std::vector::reserve If I were to write: 如果我写:

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

The vector would allocate its backing array to hold 10 MyClass s, and it won't call the default constructors. 向量将分配其后备数组以保存10个MyClass ,并且它不会调用默认构造函数。 But now I can't write arr[0] or arr[5] ; 但现在我不能写arr[0]arr[5] ; those would be out-of-bounds access into arr ( arr.size() is still 0, even though the backing array has more elements). 那些将是越界的访问arrarr.size()仍然是0,即使支持数组有更多的元素)。 To initialize the values, I'd have to call push_back or emplace_back : 要初始化值,我必须调用push_backemplace_back

arr.push_back(MyClass{});

This is often the right approach. 这通常是正确的方法。 For example, if I wanted to fill arr with random values from std::rand , I can use std::generate_n along with std::back_inserter : 例如,如果我想用std::rand随机值填充arr ,我可以使用std::generate_nstd::back_inserter

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

It's also worth noting that if I already have the values I want for arr in a container, I can just pass the begin()/end() in with the constructor: 值得注意的是,如果我已经在容器中拥有了我想要的arr值,我可以使用构造函数传递begin()/end()

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

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

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