Инициализирует ли инициализация значения сначала нулевые значения членов объекта даже с предоставленным инициализатором элемента по умолчанию в C++?

На странице инициализации значения cppreference указано следующее:

Эффекты инициализации значений:

Если T является типом класса (возможно, cv):

  • Если инициализация по умолчанию для T выбирает конструктор, а конструктор не объявлен пользователем (до C++ 11) и не предоставлен пользователем (начиная с C++ 11), объект сначала инициализируется нулем.
  • В любом случае объект инициализируется по умолчанию.

Вопрос

Означает ли это, что в следующем коде элемент x сначала инициализируется нулем со значением 0, а затем инициализируется по умолчанию со значением 10?

struct Foo
{
    int x = 10;
    int y;
};

Foo myfunc()
{
    return Foo(); // value-initialization, does x initialized with 0 first?
}

int main()
{
    myfunc();
    return 0;
}

Я не вижу никаких противоречий с тем, что указано в cppreference (ничего не сказано об инициализаторе элемента по умолчанию), значит ли это, что по адресу x будет произведено 2 записи в память? Если да, можем ли мы назвать вторую запись инициализацией? Поскольку инициализация может произойти только один раз, я бы рассматривал ее как присваивание (x = 10).

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Означает ли это, что в следующем коде элемент x сначала инициализируется нулем со значением 0, а затем инициализируется по умолчанию со значением 10?

Да, за исключением того, что член x не инициализируется по умолчанию. На втором этапе весь объект класса инициализируется по умолчанию. Но это вызывает (неявно определенный) конструктор по умолчанию, который, в свою очередь, инициализирует элемент в соответствии с инициализатором элемента по умолчанию в int x = 10;, что представляет собой инициализацию копирования из выражения 10.

Инициализация int по умолчанию оставит его значение неизменным. Вот что происходит с y, потому что у него нет инициализатора члена по умолчанию, а это означает, что неявно определенный конструктор по умолчанию будет инициализировать его по умолчанию (на втором этапе).

значит ли это, что по адресу x будет произведено 2 записи в память?

В настоящее время в стандарте C++ немного неясно (по крайней мере, насколько я могу судить), считается ли инициализация (любая из двух) «записью» (или, скорее, тем, что стандарт называет «модификацией»). См., например. Выпуск CWG 2587

Если вы спрашиваете о скомпилированном коде, то почти наверняка не будет двух записей (по крайней мере, с оптимизирующим компилятором), потому что вы никогда не наблюдаете состояние с нулевой инициализацией, и поэтому наблюдаемое поведение программы не зависит от того, не два магазина выбрасываются. Поскольку создание двух хранилищ было бы бессмысленной дополнительной работой, компилятору следует оптимизировать его только для одного хранилища.

Хотя в вашем конкретном примере компилятор оптимизирует весь объект, потому что это вообще не имеет заметного эффекта.

Если да, можем ли мы назвать вторую запись инициализацией?

Оба являются (частями) инициализацией в смысле стандарта C++.

Поскольку инициализация может произойти только один раз, я бы рассматривал ее как присваивание (x = 10).

В настоящее время в стандарте предусмотрено несколько сценариев, в которых объекты инициализируются дважды. Это проблема лишь в том смысле, что в настоящее время в спецификации иногда немного неясно, начинается ли жизнь объекта уже после завершения первой (нулевой) инициализации или только после завершения второй инициализации. В настоящее время предлагается решение считать, что срок службы начинается после первой инициализации, см. CWG, выпуск 2821.


Вот демонстрация того, как вы можете наблюдать нулевую инициализацию (при условии, что вышеуказанная проблема решена, по крайней мере, как предложено):

#include<iostream>

struct Foo
{
    int x = (std::cout << x, 1);
};

int main()
{
    auto foo = Foo();
    return 0;
}

Это надо распечатать 0. Конечно, никто не должен писать такой код, потому что, например, если вы вместо этого напишите Foo foo; сейчас, вы получите неопределенное поведение для доступа к x за пределами его жизни (или чтения неопределенного значения), поскольку Foo foo; не является инициализацией значения и, следовательно, не будет сначала инициализироваться нулем.

«потому что вы никогда не наблюдаете состояние с нулевой инициализацией». Если вы включите демо-версию OP и добавите volatile, эта потенциально дополнительная запись будет наблюдаема, и ее нельзя будет исключить с помощью правила «как если бы».

Jarod42 19.08.2024 11:56

@Jarod42 Однако на самом деле компиляторы этого не делают, см. godbolt.org/z/ncdeM8qc1. Стандарт определяет только изменчивый доступ, то есть чтение или изменение, как наблюдаемое поведение. Как я уже говорил в своем ответе, на данный момент мне кажется немного неясным, как (множественная) инициализация будет рассматриваться в этом отношении, хотя кажется очевидным, что в целом изменчивая инициализация должна быть наблюдаемым побочным эффектом.

user17732522 19.08.2024 12:13
Демо со статическими переменными (инициализированными нулем), и мы видим разницу для двух проходов для globaly, но никакой разницы для y, поэтому, насколько я понимаю, компиляторы понимают инициализацию y как один проход.
Jarod42 19.08.2024 12:25

@Jarod42 globaly инициализируется константой, поэтому для него не существует нулевой инициализации. globalx имеет динамическую инициализацию (хотя компилятору разрешено вместо этого выполнять статическую инициализацию), поэтому сначала инициализируется нулями. Но на самом деле в коде нет никакого хранилища для нулевой инициализации или инициализации констант (да и не может быть, потому что компилятор должен гарантировать, что это произойдет до выполнения любого кода). Таким образом, в этом случае компиляторы рассматривают только динамическую инициализацию, если таковая имеется, наблюдаемую, как доступ (и в этом случае я не думаю, что что-то еще имеет смысл).

user17732522 19.08.2024 12:39

Другие вопросы по теме

Может ли кто-нибудь объяснить правила времени жизни объектов и неинициализированной памяти в C++ (контекст: `std::inplace_vector`)?
Различные примечания для неинициализированного члена внутри общей лямбды в GCC 9 и GCC 10
Почему неявно сгенерированный конструктор отличается от конструктора, предоставленного пользователем?
Использование ранее установленных полей при инициализации структуры составными литералами
JavaFX не инициализирует представления
Есть ли выигрыш в производительности от использования алгоритмов c++ std(::ranges)::uninitialized_... и стоит ли не использовать constexpr?
Приводит ли наличие вектора, содержащего структуры с неинициализированными членами, к неопределенному поведению?
Попытка инициализировать объекты класса внутри структуры приводит к ошибкам сегментации
Существует ли альтернативный синтаксис для инициализации константного указателя на константные данные?
Почему Swift разрешает использование переменных перед объявлением в глобальной области, но не внутри функций?