簡體   English   中英

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

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

當你不想要它時,用零來初始化矢量不是浪費時間嗎?

我試試這段代碼:

#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 ++

為什么零呢? 順便說一句,我可以在不初始化的情況下聲明一個向量嗎?

聲明向量的更常見方法是不指定大小:

std::vector<unsigned> arr;

這不會為向量內容分配任何空間,也沒有任何初始化開銷。 元素通常使用.push_back()等方法動態添加。 如果要分配內存,可以使用reserve()

arr.reserve(SIZE);

這不會初始化添加的元素,它們不包含在向量的size()中,並且嘗試讀取它們是未定義的行為。 比較這個

arr.resize(SIZE);

它會增長向量並初始化所有新元素。

另一方面, std::array總是分配內存。 它實現了與C風格數組相同的大多數行為,除了指針的自動衰減。 這包括默認情況下不初始化元素。

默認分配器正在進行零初始化。 您可以使用不這樣做的其他分配器。 我寫了一個分配器,它在可行時使用默認構造而不是初始化。 更確切地說,它是一個名為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)...);
        }
    };
}

假設我們有一些課程:

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 ++中,有一個對象生命周期的概念。 在調用構造函數之前,該對象不存在,並且在調用析構函數后它不再存在。 std::vector<MyClass> arr(10); 調用每個元素的默認構造函數,以便存在所有對象。

重要的是要注意std::array有點特殊,因為它是按照聚合初始化規則初始化的 這意味着std::array<MyClass, 10> arr; 默認情況下,還會構造10個MyClass副本,其value = 42 但是,對於非類型類型(如unsigned ,值將是不確定的。


有一種方法可以避免調用所有默認構造函數: std::vector::reserve 如果我寫:

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

向量將分配其后備數組以保存10個MyClass ,並且它不會調用默認構造函數。 但現在我不能寫arr[0]arr[5] ; 那些將是越界的訪問arrarr.size()仍然是0,即使支持數組有更多的元素)。 要初始化值,我必須調用push_backemplace_back

arr.push_back(MyClass{});

這通常是正確的方法。 例如,如果我想用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);

值得注意的是,如果我已經在容器中擁有了我想要的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