На странице инициализации значения 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).
Означает ли это, что в следующем коде элемент 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;
не является инициализацией значения и, следовательно, не будет сначала инициализироваться нулем.
@Jarod42 Однако на самом деле компиляторы этого не делают, см. godbolt.org/z/ncdeM8qc1. Стандарт определяет только изменчивый доступ, то есть чтение или изменение, как наблюдаемое поведение. Как я уже говорил в своем ответе, на данный момент мне кажется немного неясным, как (множественная) инициализация будет рассматриваться в этом отношении, хотя кажется очевидным, что в целом изменчивая инициализация должна быть наблюдаемым побочным эффектом.
globaly
, но никакой разницы для y
, поэтому, насколько я понимаю, компиляторы понимают инициализацию y
как один проход.
@Jarod42 globaly
инициализируется константой, поэтому для него не существует нулевой инициализации. globalx
имеет динамическую инициализацию (хотя компилятору разрешено вместо этого выполнять статическую инициализацию), поэтому сначала инициализируется нулями. Но на самом деле в коде нет никакого хранилища для нулевой инициализации или инициализации констант (да и не может быть, потому что компилятор должен гарантировать, что это произойдет до выполнения любого кода). Таким образом, в этом случае компиляторы рассматривают только динамическую инициализацию, если таковая имеется, наблюдаемую, как доступ (и в этом случае я не думаю, что что-то еще имеет смысл).
«потому что вы никогда не наблюдаете состояние с нулевой инициализацией». Если вы включите демо-версию OP и добавите
volatile
, эта потенциально дополнительная запись будет наблюдаема, и ее нельзя будет исключить с помощью правила «как если бы».