Размещение новых и неинициализированных членов POD

Гарантирует ли стандарт С++, что неинициализированные члены POD сохраняют свое предыдущее значение после размещения нового?

Или, точнее, всегда будет выполняться следующее утверждение в соответствии с С++ 11?

#include <cstdlib>
#include <cassert>

struct Foo {
    int alpha; // NOTE: Uninitialized
    int beta = 0;
};

int main()
{
    void* p = std::malloc(sizeof (Foo));
    int i = some_random_integer();
    static_cast<Foo*>(p)->alpha = i;
    new (p) Foo;
    assert(static_cast<Foo*>(p)->alpha == i);
}

Является ли ответ одинаковым для С++ 03?

Ответ 1

Гарантирует ли стандарт С++, что неинициализированные члены POD сохраняют свое предыдущее значение после размещения нового?

Будет ли выполняться следующее утверждение в соответствии с С++ 11?

Нет.

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

[C++11: 5.3.4/15]: Новое выражение, создающее объект типа T, инициализирует этот объект следующим образом:

  • Если новый инициализатор опущен, объект инициализируется по умолчанию (8.5); если инициализация не выполняется, объект имеет неопределенное значение.
  • В противном случае новый-инициализатор интерпретируется в соответствии с правилами инициализации 8.5 для прямой инициализации.

[C++11: 8.5/6]: По умолчанию инициализировать объект типа T означает:

  • , если T является (возможно, cv-квалифицированным) классом (раздел 9), конструктор по умолчанию для T называется (и инициализация плохо сформирована, если T не имеет доступного конструктора по умолчанию);
  • if T - тип массива, каждый элемент инициализируется по умолчанию;
  • в противном случае инициализация не выполняется.

[C++11: 12.1/6]: Конструктор по умолчанию, который по умолчанию и не определен как удаленный, неявно определяется, когда он используется odr (3.2) для создания объекта его типа класса (1.8) или когда он явно дефолт после его первого объявления, Неявно определенный конструктор по умолчанию выполняет набор инициализаций класса, который был бы выполненный пользовательским конструктором по умолчанию для этого класса без инициализатора ctor (12.6.2) и пустого составного оператора.

[C++11: 12.6.2/8]: В конструкторе без делегирования , если данный нестатический член данных или базовый класс не обозначен mem-initializer-id (включая случай, когда нет mem-initializer-list, потому что у конструктора нет ctor-initializer), и сущность не является виртуальным базовым классом абстрактного класса (10.4), то

  • если объект является нестатическим членом данных, у которого есть инициализатор скобок или равный, объект инициализируется, как указано в 8.5;
  • в противном случае, если объект является вариантом (9.5), инициализация не выполняется;
  • , в противном случае объект инициализируется по умолчанию (8.5).

( NB. первый вариант в 12.6.2/8 - это то, как обрабатывается ваш член beta)

[C++11: 8.5/6]: По умолчанию инициализировать объект типа T означает:

  • Если T является классом класса (возможно, cv-qualit) (раздел 9), вызывается конструктор по умолчанию для T (и инициализация плохо сформирована, если T не имеет доступного конструктора по умолчанию);
  • if T - тип массива, каждый элемент инициализируется по умолчанию;
  • , в противном случае инициализация не выполняется.

[C++11: 8.5/11]: Если для объекта не задан инициализатор, объект инициализируется по умолчанию; Если инициализация не выполняется, объект с автоматической или динамической продолжительностью хранения имеет неопределенное значение.

Компилятор может выбрать нулевое (или иным образом изменить) базовую память во время выделения. Например, Visual Studio в режиме отладки, как известно, записывает в память распознаваемые значения, такие как 0xDEADBEEF, чтобы помочь отладке; в этом случае вы, скорее всего, увидите 0xCDCDCDCD, которые они используют для обозначения "чистой памяти" (ссылка).

Будет ли это, в данном случае? Я не знаю. Я не думаю, что мы можем знать.

Мы знаем, что С++ не запрещает это, и я считаю, что это приводит нас к завершению этого ответа.:)


Является ли ответ одинаковым для С++ 03?

Да, хотя с помощью немного другой логики:

[C++03: 5.3.4/15]: Новое выражение, создающее объект типа T, инициализирует этот объект следующим образом:

  • Если новый-инициализатор опущен:
    • Если T является (возможно, cv-квалифицированным) классом не-POD (или его массивом), объект инициализируется по умолчанию (8.5). Если T - это тип, определенный константой, тип базового класса должен иметь объявленный пользователем конструктор по умолчанию.
    • В противном случае созданный объект имеет неопределенное значение. Если T - это тип, специфичный для const, или тип (класс) POD класса (или его массив), содержащий (непосредственно или косвенно) член типа const-type, программа плохо сформирована;
  • Если новый-инициализатор имеет форму (), элемент инициализируется значением (8.5);
  • Если новый-инициализатор имеет вид (expression-list) и T - тип класса, вызывается соответствующий конструктор, используя expression-list в качестве аргументов (8.5);
  • Если новый-инициализатор имеет вид (expression-list) и T - это арифметический, перечисляемый, указательный или указательный-на-член, а expression-list содержит ровно одно выражение, то объект инициализируется (возможно преобразованное) значение выражения (8.5);
  • В противном случае новое выражение плохо сформировано.

Теперь все это была моя строгая интерпретация правил инициализации.

Говоря практически, я думаю, что вы, вероятно, правы, видя потенциальный конфликт с определением синтаксиса размещения operator new:

[C++11: 18.6.1/3]: Примечания: Умышленно не выполняет никаких других действий.

В следующем примере объясняется, что размещение new "может быть полезно для построения объекта по известному адресу".

Однако на самом деле он не говорит об общем использовании построения объекта по известному адресу без изменения значений, которые уже были там, но фраза "не выполняет никаких других действий" действительно предполагает, что ваше намерение заключается в том, что ваш "неопределенное значение" будет тем, что было в памяти ранее.

В качестве альтернативы, он может просто запретить самому оператору предпринимать какие-либо действия, оставив распределителя свободным. Мне кажется, что важный момент, который пытается сделать стандарт, заключается в том, что не выделяется новая память.

Несмотря на это, доступ к этим данным вызывает поведение undefined:

[C++11: 4.1/1]: Значение gl (3.10) нефункционного типа без массива T может быть преобразовано в prvalue. Если T является неполным типом, программа, которая требует этого преобразования, плохо сформирована. Если объект, к которому относится glvalue, не является объект типа T и не является объектом типа, полученного из T, или , если объект не инициализирован, программа, которая требует этого преобразования, имеет поведение undefined. Если T является неклассовым типом, тип prvalue является cv-неквалифицированной версией T. В противном случае тип prvalue равен T.

Так что это не имеет большого значения: вы не могли в должной мере соблюдать исходное значение.

Ответ 2

С++ 11 12.6.2/8 "Инициализация баз и членов" говорит:

В конструкторе без делегирования, если данный нестатический член данных или базовый класс не обозначается идентификатором mem-initializer-id (включая если нет mem-initializer-list, потому что конструктор не имеет ctor-инициализатора), и сущность не является виртуальным базовым классом абстрактный класс (10.4), то

  • если объект является нестатистическим элементом данных, у которого есть инициализатор скобок или равный-инициализатор, объект инициализируется, как указано в 8.5;
  • в противном случае, если объект является вариантом (9.5), инициализация не выполняется;
  • , в противном случае объект инициализируется по умолчанию (8.5).

Инициализация по умолчанию на int ничего не делает (8.5/6 "Инициализаторы" ):

Для инициализации объекта типа T по умолчанию:

  • если T является (возможно, cv-квалифицированным) типом класса (раздел 9), вызывается конструктор по умолчанию для T (и инициализация плохо сформированный, если T не имеет доступного конструктора по умолчанию);
  • если T - тип массива, каждый элемент инициализируется по умолчанию;
  • , иначе инициализация не выполняется.

Итак, член alpha следует оставить в покое.