C++ 值初始化自定义容器的项目

C++ value initialize items of a custom container

让我们以自定义 vector 实现为例:

template<typename Object>
class myVector {
public:
    explicit myVector(int size = 0) :
        _size{ size },
        _capasity{ size + SPARE_CAPACITY }
    {
        _buff = new Object[_capasity];
        if (_size > 0) {
            for (int i = 0; i < _size; i++) {
                //_buff[i] = 0;
            }
        }
    }

// more code

private:
    Object * _buff = nullptr;
    int _size;
    int _capasity;
};

所以我的问题是,如何对 myVector 进行值初始化,以防我将其初始化为:

int main() {
    myVector<int> v02(5);                   
}

这里,它包含5个int值,所以我需要它全为零;与其他类型相同。我注释掉了 _buff[i] = 0;,因为它特定于 int。请给我一些提示。

就这么简单

for (int i = 0; i < _size; i++)
    _buff[i] = Object{};

或者,您可以摆脱循环并在此处添加一对 {}(或 ()):

_buff = new Object[_capasity]{};
//                           ^^

但此选项将值初始化所有 _capasity 个对象,而不是第一个 _size 个对象,如 所述。


此外,请注意,如果您想模仿 std::vector 的行为,您需要分配原始存储(可能 std::aligned_storage)并手动调用构造函数(通过 placement-new)和析构函数。

如果 Object 是 class 类型,_buff = new Object[_capasity]; 为所有 _capasity 对象调用默认构造函数,而不是为前 _size 个对象调用默认构造函数,因为 std::vector 确实如此。

调用时注意

        _buff = new Object[_capasity];

(顺便说一句,为什么你把这个初始化从 init-list 移到构造函数体中?)你已经有默认初始化的 _capasity 对象。默认初始化在这里具有以下效果:虽然标量类型的元素将保持未初始化状态(并从 UB 中读取),但对于 class 类型,您已经调用了 _capasity 构造函数。

为了避免不必要的构造,您有以下可能的选择,其中包括:

  1. 使用std::aligned_alloc分配非初始化内存:

    explicit myVector(std::size_t size = 0) :
        size_{ size }
        , capacity_{ size + SPARE_CAPACITY }
        , buff_{std::aligned_alloc(alignof(Object), _capacity)}
    {
        if(!buff_) throw std::bad_alloc();
        if(size) new (buff_) Object[size]{}; // empty braces answer your original query
    }
    

    再次记住当 vector 增长时 buff_ 应该被 aligned_alloced(对于普通类型可以 std::realloc()ed),并且在析构函数中它应该是 std::free()d — 在此之前,应该销毁其中的 size_ 个对象(显式调用 ~Object())。

  2. buff_ 的类型更改为更简单但正确对齐的类型:

        using Storage = std::aligned_storage_t<sizeof(Object), alignof(Object)>;
        Storage *buff_;
        Object *data_ = nullptr;
    public:
        explicit myVector(std::size_t size = 0) :
            size_{ size }
            , capacity_{ size + SPARE_CAPACITY }
            , buff_{new Storage(_capacity)}
        {
            if(size) data_ = new (buff_) Object[size]{};
        }
    

    同样,在析构函数中,对象应该被手动销毁,但是这次buff_可以简单地delete[]d之后。