如何在不初始化每个元素的情况下为数组保留空间

How can you reserve space for an array without initializing each element?

本文关键字:情况下 数组 空间 保留 元素 初始化      更新时间:2023-10-16

下面的代码将使用默认的Foo构造函数构造10个Foo对象的数组:

Foo foo[10];

但我不想这样做,我有 Havy Foo 构造函数,稍后我将一个接一个地再生所有 Foo 对象并分配(复制)它到Foo数组的元素,所以没有意义要初始化数组,我只想为它保留空间并稍后设置它元素。就像

int foo[10]

当 foo 的元素在没有 ={} 的情况下不会被初始化时。 如何在不使用 std 命名空间的情况下执行此操作(我将在不支持 std 的 PC 和 CUDA 上使用代码)?

你可以做所有好的动态容器做的事情:单独的内存分配和对象构造。

// allocate memory
char * space[10 * sizeof(Foo)];
// make sure it's aligned for our purposes
// see comments; this isn't actually specified to work
assert(reinterpret_cast<uintptr_t>(space) % alignof(Foo) == 0);
// populate 4th and 7th slots
Foo * p = ::new (space + 3 * sizeof(Foo)) Foo('x', true, Blue);
Foo * q = ::new (space + 6 * sizeof(Foo)) Foo('x', true, Blue);
// ...
// clean up when done
q->~Foo();
p->~Foo();

使用自动存储时,棘手的部分是使存储对齐到适合数组元素类型对齐的地址。有几种方法可以实现此目的;我以后会对它们进行讨论:

  1. std::align(感谢@Simple):

    char large_space[10 * sizeof(Foo) + 100];
    std::size_t len = sizeof large_space;
    void * space_ptr = large_space;
    Foo * space = static_cast<Foo *>(std::align(alignof(Foo), 10 * sizeof(Foo), space, len));
    assert(space != nullptr);
    // construct objects in &space[i]
    
  2. alignas限定space的定义

    alignas(Foo) char space[10 * sizeof(Foo)];
    
  3. 使space成为一系列合适的std::aligned_storage专业化(感谢@RMF)

    std::aligned_storage<sizeof(Foo), alignof(Foo)>::type space[10];
    Foo *p = new (&space[3]) Foo('x', true, Blue);
    Foo *q = new (&space[6]) Foo('x', true, Blue);
    

最简单的方法是使用 std::vector

std::vector<Foo> foo;

如果需要,可以调用foo.reserve(10)来分配内存。如果你有 C++11,你可以使用 foo.emplace_back(/*args*/) 将对象直接创建到数组中,无需复制。

如果您不想/不能使用std::vector,您可以手动执行此操作:

unsigned char foo[10 * sizeof(Foo)];

然后要构造对象,请使用放置 new

int x = ...;
Foo *fooX = new (foo[x * sizeof(Foo)) Foo(/*args to the constructor*/);

但是,您最终将不得不手动调用析构函数:

fooX->~Foo();

但请注意,此解决方案在字节数组的对齐方面可能存在困难。您可能更喜欢使用malloc()来确保:

unsigned char *foo = malloc(10 * sizeof(Foo));

如果你不会在数组中留下漏洞,你可以简单地将std::vectorreservepush_back一起使用。

如果你想在数组中出现孔...您可以使用一些分配器获得大小合适且对齐正确的内存块,然后使用放置新等...但是您必须跟踪哪些洞被填满,哪些洞没有被填满。 boost::optional已经做到了这一切,所以std::vector<boost::optional<Foo>>会很好地服务并为您省去一堆麻烦。

最简单的

方法是使用std::vector

std::vector<Foo> foovec;
foovec.reserve(10);

所以,有 10 个 Foo 型元素的地方,但它们还没有被构建。

此外,您可以使用placing-new手动编写类似的东西

char* place = static_cast<char*>(::operator new(sizeof(Foo) * 10));

然后填充放置新运算符。

Foo* f1 = new (place) Foo(...);
Foo* f2 = new (place + sizeof(Foo)) Foo(...);
//
f1->~Foo();
f2->~Foo();
::operator delete(place);

我的解决方案考虑了这些约束(OP 和对不同答案的一些评论暗示):

  1. 我们不能使用 STL;和
  2. 我们想使用堆栈内存(而不是堆)。

它还考虑了其他人提到的对齐问题,鉴于 OP 被标记为 C++11,我们可以使用 C++11。

首先为未初始化它的Foo引入存储:

union FooStorage {
    Foo data;
    FooStorage() {
    }
    template <typename... Args>
    void init(Args&&... args) {
        new (&data) Foo{static_cast<Args&&>(args)...};
    }
    ~FooStorage() {
        data.~Foo();
    }
};

请注意,构造函数不会初始化data但析构函数会销毁它。您必须在销毁之前初始化data(使用 init())。(从OP来看,我认为它会发生,但我必须指出这种危险。

请注意,使用 { ... }( ... ) 的初始化并不等效。你必须根据Foo的构造函数做出决定。

以这种方式分配 10 Foo 秒的堆栈内存

FooStorage buffer[10];

要初始化第 i Foo

buffer[i].init(/* constructor arguments */);

要使用i -th Foo,例如,调用它的方法do_something

buffer[i].data.do_something();

这是基本思想。可以做很多改进来FooStorage

数组boost::optional<Foo>或 C++14 std::optional<Foo>

boost::optional<Foo> foo[10];