Создание неинициализированного массива элементов с конструкторами по умолчанию?

Учитывая класс Foo, который имеет некоторый конструктор по умолчанию, инициализирующий значение:

class Foo {
private:
    uint32_t x;

public:
    constexpr Foo()
        : x { 3 }
    {}
    // ... and some other constructors
};

Мне нужно выделить массив этих Foo. Я не хочу, чтобы конструкторы элементов массива по умолчанию запускались, потому что позже я все равно буду явно инициализировать каждый элемент. Что-то вроде этого:

Foo foos[20000];

for (int i = 0; i < 20000; ++i) {
    foos[i] = init(i);
}

Есть ли способ получить такой неинициализированный массив Foo, учитывая, что нам не разрешено изменять конструктор по умолчанию Foo на неинициализирующий?

Кстати, именно так вы бы создали неинициализированный массив в D:

Foo[20000] foos = void;

...и вот то же самое в Rust:

let mut foos: [Foo; 20000] = unsafe { std::mem::uninitialized() };

person kmky    schedule 12.03.2015    source источник
comment
@zch: Остерегайтесь выравнивания.   -  person Deduplicator    schedule 12.03.2015
comment
Вы профилировали, что вызовы конструктора по умолчанию вызвали проблемы с производительностью? (Я предполагаю, что именно поэтому вы заинтересованы в этом)   -  person Neil Kirk    schedule 12.03.2015
comment
@zch, который нарушает строгое сглаживание, к сожалению   -  person M.M    schedule 12.03.2015
comment
@NeilKirk: Нет, у меня нет особых проблем с этим. Мне просто интересно узнать, как программировать подобные вещи без лишних накладных расходов (в конце концов, это C++ :).   -  person kmky    schedule 12.03.2015
comment
На самом деле, у cppreference есть пример решения подобной проблемы en.cppreference.com/w/cpp. /types/aligned_storage   -  person zch    schedule 12.03.2015
comment
@MattMcNabb, char не имеет строгих проблем с псевдонимами. Однако может потребоваться размещение new.   -  person zch    schedule 12.03.2015
comment
@zch: я действительно не уверен, но я думаю, что ваш пример может вызвать неопределенное поведение из-за некоторых правил C ++, которые называются строгим псевдонимом.   -  person kmky    schedule 12.03.2015
comment
@zch char может использоваться для псевдонима других типов, но другой тип не может использоваться для псевдонима char   -  person M.M    schedule 12.03.2015
comment
@kmky C++ имеет инкапсуляцию; если вы определяете свой класс так, что единственным способом его создания является инициализация значения равным 3, то другого способа его создания не существует. (Звучит тавтологично, потому что это так!). Вы можете выделить память, которая еще не содержит объектов (так работает vector::reserve).   -  person M.M    schedule 12.03.2015
comment
@МэттМакНабб. Хорошо, тогда я думаю, что размещение new имеет дело с этим. Тогда ваша программа вообще не будет пытаться получить доступ к сохраненному значению объекта через glvalue до тех пор, пока объект соответствующего типа не будет создан с помощью new. Это в основном то, что сделало бы любое использование std::aligned_storage.   -  person zch    schedule 12.03.2015
comment
@MattMcNabb: Я не собирался инкапсулировать это так. Я просто не писал никаких других конструкторов, потому что не думал, что они имеют значение. Во что бы то ни стало, класс Foo мог бы иметь, например, следующий неинициализирующий конструктор не по умолчанию: Foo(Uninitialized) {} и было бы вспомогательное перечисление для вызова этого: enum Uninitialized { uninit };. Я не понимаю, как такое ослабление инкапсуляции поможет решить мою проблему.   -  person kmky    schedule 12.03.2015
comment
Предложение Re zch - я согласен, что проблемы с псевдонимами могут возникнуть, если память изменилась через char*s на arr и через Foo*, например foos - если только этот последний используется для инициализации/доступа/изменения памяти, есть ли все еще потенциальная проблема? я бы не рискнул....   -  person Tony Delroy    schedule 12.03.2015
comment
@MattMcNabb: std::array не имеет конструкторов.   -  person kmky    schedule 12.03.2015
comment
Псевдоним @TonyD говорит о доступе, который включает как чтение, так и запись. См. 3.10/10. Массив беззнаковых символов — это объект, динамический тип которого — массив беззнаковых символов.   -  person M.M    schedule 12.03.2015
comment
@kmky д'о. Небольшая слабость языка заключается в том, что массивам в стиле C нельзя дать инициализатор для применения ко всем элементам. Вы можете написать Foo foos[20000] = { uninit, uninit, uninit, uninit, etc. - либо копирование-вставка, либо использование другой программы для генерации исходного кода для вас. Другим вариантом было бы получить от Foo и предоставить конструктор по умолчанию, который делает то, что вы хотите; а затем получите свой массив производного класса (который при необходимости может сократиться до Foo).   -  person M.M    schedule 12.03.2015
comment
@zch: я думаю, что пример в предоставленной вами ссылке делает именно то, что Я хочу сделать. Он размещается в стеке без инициализации, а затем размещается для инициализации элементов. Спасибо.   -  person kmky    schedule 12.03.2015
comment
@MattMcNabb: спасибо за ссылку на 3.10/10.   -  person Tony Delroy    schedule 12.03.2015


Ответы (3)


Если вы используете C++11, вы можете использовать std::vector и emplace_back()

vector<Foo> foos;
for(int i = 0; i < 20000; ++i)
    foos.emplace_back( /* arguments here */);
person yizzlez    schedule 12.03.2015
comment
Это работает, но если целью использования массива в стиле C и избегания использования конструкторов по умолчанию была производительность (почему еще?), использование вектора может быть медленнее. - person Neil Kirk; 12.03.2015
comment
Профиль @NeilKirk, прежде чем предположить ... Вектор может изначально reserve(20000);, чтобы избежать ненужных распределений. Во всяком случае, я не вижу лучшего варианта, чем этот. - person M.M; 12.03.2015
comment
@MattMcNabb Я согласен с вами, однако, учитывая контекст вопроса, я предположил, что он искал микрооптимизации с учетом уже определенного профилирования. Также разрешено ли компилятору делать это по стандарту? - person Neil Kirk; 12.03.2015
comment
вектор с запасом должен быть оптимальным. он захватывает необработанные байты, используя распределитель по умолчанию, и создает элементы с новым размещением по мере необходимости. - person M.M; 12.03.2015
comment
Компилятор может делать все, что не меняет наблюдаемое поведение программ, хотя, по моему опыту, они не сделали бы reserve без специального кодирования. - person M.M; 12.03.2015
comment
Что, если я хочу разместить массив в стеке, а не в куче. Можно ли проделать ту же магию, что и std::vector под капотом (с reserve и emplace_back), с выделенным в стеке блоком неинициализированной памяти? - person kmky; 12.03.2015
comment
@MattMcNabb Вот что я говорю. Я никогда не замечал, чтобы мой компилятор резервировал без моего ведома. - person Neil Kirk; 12.03.2015
comment
@kmky Вам нужно использовать стек только в том случае, если у вас проблемы с производительностью, но вы сказали, что это не так. - person Neil Kirk; 12.03.2015
comment
@NeilKirk: у меня нет конкретной проблемы. Моя цель — научиться делать это на тот случай, если я когда-нибудь столкнусь с проблемой производительности, которую можно решить, не инициализируя каждый элемент дважды. - person kmky; 12.03.2015
comment
@kmky Я знаю, что это не идеально и может вас беспокоить, но я никогда не сталкивался с проблемой производительности, связанной с конструкторами по умолчанию в массиве, который устанавливает целое число. Я бы не стал сильно заморачиваться по этому поводу :) - person Neil Kirk; 12.03.2015

Возможно, вы ищете std::get_temporary_buffer:

int main()
{
  size_t n = 20000;
  auto buf = std::get_temporary_buffer<Foo>(n);
  if (buf.second<n) {
    std::cerr << "Couldn't allocate enough memory\n";
    return EXIT_FAILURE;
  }

  // ...

  std::raw_storage_iterator<Foo*,Foo> iter(buf.first);
  for (int i = 0; i < n; ++i) {
    *iter++ = Foo();
  }

  // ...

  std::return_temporary_buffer(buf.first);
}
person Vaughn Cato    schedule 12.03.2015
comment
Насколько эффективны эти временные буферы, зависит от реализации. Я проверил одну из Visual Studio, и она только что тайно назвалась новой. - person Neil Kirk; 12.03.2015

Возможно, это более точно отвечает на поставленный вопрос?

#include <type_traits>

class Foo {
private:
    uint32_t x;

public:
    constexpr Foo()
        : x { 3 }
    {}

    constexpr Foo(uint32_t n)
        : x { n * n }
    {}
};

    // ...and then in some function:

    typename std::aligned_storage<sizeof(Foo), alignof(Foo)>::type foos[20000];

    for (int i = 0; i < 20000; ++i) {
        new (foos + i) Foo(i);
    }

Недостаток, по-видимому, в том, что вы можете использовать только конструктор для инициализации этих элементов, а не свободную функцию или что-то еще.

Вопрос: Могу ли я получить доступ к этим Foo следующим образом:

    Foo* ptr = reinterpret_cast<Foo*>(foos);
    ptr[50] = Foo();
person kmky    schedule 12.03.2015
comment
Объекты могут быть созданы только через их конструктор, это не столько недостаток, сколько работа по назначению. Ваш код доступа кажется правильным, если вы, конечно, делаете это после цикла построения. - person M.M; 12.03.2015