如果不想使用零初始化向量,是否浪费时间?
我尝试以下代码:
#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;
}
Run Code Online (Sandbox Code Playgroud)
我得到的输出:
与矢量
$ g++ -std=c++11 -D VECTOR test.cpp -o test && ./test
0 0 0 0 0 0 0 0 0 0
Run Code Online (Sandbox Code Playgroud)
与数组
g++ -std=c++11 test.cpp -o test && ./test
-129655920 32766 4196167 0 2 0 4196349 0 1136 0
Run Code Online (Sandbox Code Playgroud)
我也尝试使用clang ++
那为什么要零呢?顺便说一句,我可以不初始化就声明一个向量吗?
声明向量的更常见方法是不指定大小:
std::vector<unsigned> arr;
Run Code Online (Sandbox Code Playgroud)
这不会为矢量内容分配任何空间,也没有任何初始化开销。通常使用诸如之类的方法动态添加元素.push_back()。如果要分配内存,可以使用reserve():
arr.reserve(SIZE);
Run Code Online (Sandbox Code Playgroud)
这不会初始化添加的元素,它们不包含在size()向量的中,并且尝试读取它们是未定义的行为。比较一下
arr.resize(SIZE);
Run Code Online (Sandbox Code Playgroud)
从而增加向量并初始化所有新元素。
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)...);
}
};
}
Run Code Online (Sandbox Code Playgroud)
假设我们有一些类:
class MyClass {
int value;
public:
MyClass() {
value = 42;
}
// other code
};
Run Code Online (Sandbox Code Playgroud)
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);
Run Code Online (Sandbox Code Playgroud)
该向量将分配其支持数组以容纳 10MyClass秒,并且不会调用默认构造函数。但现在我不能写arr[0]或arr[5];这些将是越界访问arr(arr.size()仍然是 0,即使支持数组有更多元素)。要初始化这些值,我必须调用push_backor emplace_back:
arr.push_back(MyClass{});
Run Code Online (Sandbox Code Playgroud)
这通常是正确的方法。例如,如果我想填充arr中的随机值std::rand,我可以std::generate_n与 一起使用std::back_inserter:
std::vector<unsigned> arr;
arr.reserve(10);
std::generate_n(std::back_inserter(arr), 10, std::rand);
Run Code Online (Sandbox Code Playgroud)
还值得注意的是,如果我已经arr在容器中拥有了所需的值,我可以将其传递begin()/end()给构造函数:
std::vector<unsigned> arr{values.begin(), values.end()};
Run Code Online (Sandbox Code Playgroud)