Подтвердить что ты не робот

Определено ли поведение для ссылки на ранний элемент из более позднего выражения элемента во время инициализации агрегата?

Рассмотрим следующее:

struct mystruct
{
    int i;
    int j;
};

int main(int argc, char* argv[])
{
    mystruct foo{45, foo.i};   

    std::cout << foo.i << ", " << foo.j << std::endl;

    return 0;
}

Обратите внимание на использование foo.i в списке агрегатов-инициализаторов.

g++ 5.2.0 выходы

45, 45

Является ли это четко определенным поведением? Является ли foo.i в этом агрегаторе-инициализаторе всегда гарантировано ссылаться на созданный структурой элемент i&foo.i будет ссылаться на этот адрес памяти, например)?

Если я добавлю явный конструктор в mystruct:

mystruct(int i, int j) : i(i), j(j) { }

Затем я получаю следующие предупреждения:

main.cpp:15:20: warning: 'foo.a::i' is used uninitialized in this function [-Wuninitialized]
     a foo{45, foo.i};
                ^
main.cpp:19:34: warning: 'foo.a::i' is used uninitialized in this function [-Wuninitialized]
     cout << foo.i << ", " << foo.j << endl;

Компиляция кода и выход:

45, 0

Ясно, что это делает что-то другое, и я предполагаю, что это поведение undefined. Это? Если да, то почему разница между этим и отсутствием конструктора? И как я могу получить начальное поведение (если это было четкое поведение) с определяемым пользователем конструктором?

4b9b3361

Ответ 1

В вашем втором случае поведение undefined, вы больше не используете агрегатную инициализацию, оно все еще содержит инициализацию списка, но в этом случае вы вызываете вызываемый пользователем конструктор. Чтобы передать второй аргумент вашему конструктору, ему нужно оценить foo.i, но он еще не инициализирован, так как вы еще не вошли в конструктор и, следовательно, вы создаете неопределенное значение и создание неопределенного значения - undefined поведение.

У нас также есть раздел 12.7 Конструкция и разрушение [class.cdtor], в котором говорится:

Для объекта с нетривиальным конструктором, ссылающегося на любой нестатический член или базовый класс объекта перед тем как конструктор начнет выполнение результатов в undefined поведение [...]

Итак, я не вижу способа заставить ваш второй пример работать как ваш первый пример, предполагая, что первый пример действительно действителен.

Ваш первый случай кажется, что он должен быть хорошо определен, но я не могу найти ссылку в проекте стандарта, который, кажется, делает это явным. Возможно, это дефект, но в противном случае это будет undefined поведение, поскольку стандарт не определяет поведение. Стандарт говорит нам, что инициализаторы оцениваются по порядку, а побочные эффекты секвенированы, из раздела 8.5.4 [dcl.init.list]:

В списке инициализаторов списка с привязкой-инициализацией предложения инициализатора, включая все, что получается из пакета разложения (14.5.3), оцениваются в том порядке, в котором они появляются. То есть вычисление всех значений и побочный эффект, связанный с заданным предложением инициализатора, секвенирован перед каждым вычислением значения и стороной эффект, связанный с любым предложением инициализатора, которое следует за ним в разделенном запятыми списке списка инициализаторов. [...]

но у нас нет явного текста, говорящего о том, что члены инициализируются после каждого элемента.

MSalters утверждает, что раздел 1.9, который гласит:

Доступ к объекту, обозначенному изменчивым значением glvalue (3.10), изменением объекта, вызовом библиотечного ввода-вывода функция или вызов функции, которая выполняет любую из этих операций , являются побочными эффектами, которые являются изменениями в состояние среды исполнения. [...]

в сочетании с:

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

Достаточно, чтобы гарантировать, что каждый член агрегата инициализируется, когда оцениваются элементы списка инициализатора. Хотя это не будет применяться до С++ 11, поскольку порядок оценки списка инициализаторов не указан.

Для справки, если стандарт не налагает требования, поведение undefined из раздела 1.3.24, которое определяет поведение undefined:

для которого настоящий международный стандарт не предъявляет никаких требований [Примечание: поведение undefined можно ожидать, если в этом Международном стандарте отсутствует явное определение поведение или [...]

Обновить

Johannes Schaub указывает отчет о дефекте 1343: последовательность инициализации неклассификации и темы обсуждения std Является ли копирование-инициализация агрегатного члена связанной с соответствующим предложением-инициализатором? и Является ли копирование инициализации агрегата член, связанный с соответствующим параметром-инициализатором?, которые являются релевантными.

Они в основном указывают, что первый случай в настоящее время не указан, я quote Richard Smith:

Итак, единственный вопрос - это побочный эффект инициализации s.i "связанный с" оценкой полного выражения "5"? я думаю единственное разумное предположение состоит в том, что оно: если 5 инициализировали член типа класса, вызов конструктора, очевидно, будет частью полное выражение по определению в [intro.execution] p10, поэтому оно естественно предположить, что то же самое верно для скалярных типов.

Однако, я не думаю, что стандарт действительно прямо говорит об этом в любом месте.

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

Ответ 2

Из [dcl.init.aggr] 8.5.1 (2)

Когда агрегат инициализируется списком инициализатора, как указано в 8.5.4, элементы списка инициализаторов берутся как инициализаторы для членов агрегата, увеличивая индекс или порядок членов. Каждый член инициализируется копией из соответствующего предложения initializer.

акцент мой

И

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

Позволяет мне полагать, что каждый член класса будет инициализирован в том порядке, в котором они объявлены в списке инициализаторов, и поскольку foo.i инициализируется до того, как мы его инициализируем для инициализации j, это должно быть определено поведение.

Это также поддерживается [intro.execution] 1.9 (12)

Доступ к объекту, обозначенному изменчивым значением glvalue (3.10), изменением объекта, вызовом функции ввода-вывода библиотеки или вызовом функции, которая выполняет любую из этих операций эффекты, которые являются изменениями состояния среды выполнения.

акцент мой

В вашем втором примере мы не используем инициализацию агрегата, но инициализируем список. [dcl.init.list] 8.5.4 (3) имеет

Список-инициализация объекта или ссылки типа T определяется следующим образом:
[...]
- В противном случае, если T - тип класса, рассматриваются конструкторы. Соответствующие конструкторы перечислены и лучший выбирается с помощью разрешения перегрузки (13.3, 13.3.1.7).

Итак, теперь мы будем называть ваш конструктор. При вызове конструктора foo.i не был инициализирован, поэтому мы копируем неинициализированную переменную, которая является undefined.

Ответ 3

Моя первая идея была UB, но вы полностью в общем случае инициализации. Проект n4296 для спецификации С++ 11 явно указан в пункте 8.5.1 Aggregates [dcl.init.aggr]:

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

Далее:

Когда агрегат инициализируется списком инициализаторов, как указано в 8.5.4, элементы списка инициализаторов берутся как инициализаторы для членов совокупности, в возрастающем индексе или порядке членов

(подчеркните мое)

Я понимаю, что mystruct foo{45, foo.i}; сначала инициализирует foo.i с 45, затем foo.j с foo.i.

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

Ответ 4

как я могу получить начальное поведение (если это было корректное поведение) с определяемым пользователем конструктором?

Передача параметра по ссылке для этого параметра, которая ссылается на ранее инициализированный параметр объекта-объекта, следующим образом:

 mystruct(int i, int& j):i(i),j(j)