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

Может ли инициализация выражения использовать сама переменная?

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

#include <iostream>

struct Data
{
    int x, y;
};

Data fill(Data& data)
{
    data.x=3;
    data.y=6;
    return data;
}

int main()
{
    Data d=fill(d);
    std::cout << "x=" << d.x << ", y=" << d.y << "\n";
}

Здесь d инициализируется копией из возвращаемого значения fill(), но fill() записывается в d непосредственно перед возвратом его результата. Что меня беспокоит, так это то, что d используется не тривиально до инициализации, а использование неинициализированных переменных в некоторых (все?) Случаях приводит к поведению undefined.

Итак, этот код действителен или имеет поведение undefined? Если оно действительно, будет ли поведение undefined раз Data перестать быть POD или в каком-то другом случае?

4b9b3361

Ответ 1

Это не похоже на действительный код. Это похоже на случай, описанный в вопросе: Проводит объект С++ в свой собственный конструктор legal?, хотя в этом случае код был действительным. Механика не идентична, но основополагающие рассуждения могут, по крайней мере, начать нас.

Начнем с отчет о дефекте 363, в котором говорится:

И если да, то какова семантика самоинициализации UDT? Например

 #include <stdio.h>

 struct A {
        A()           { printf("A::A() %p\n",            this);     }
        A(const A& a) { printf("A::A(const A&) %p %p\n", this, &a); }
        ~A()          { printf("A::~A() %p\n",           this);     }
 };

 int main()
 {
  A a=a;
 }

можно скомпилировать и распечатать:

A::A(const A&) 0253FDD8 0253FDD8
A::~A() 0253FDD8

и предлагаемая резолюция была:

3.8 [basic.life], пункт 6, указывает, что ссылки здесь действительны. До этого ему разрешалось брать адрес объекта класса полностью инициализирован, и он разрешил передать его в качестве аргумента для опорный параметр, если ссылка может напрямую связываться. [...]

Итак, хотя d не полностью инициализирован, мы можем передать его как ссылку.

Здесь мы начинаем беспокоиться:

data.x=3;

Проект стандартного раздела С++ 3.8 (в том же разделе и параграфе, в котором указаны цитаты с дефектом) говорит (акцент мой):

Аналогично, до начала жизни объекта, но после хранилище, которое будет занимать объект, или, после срок службы объекта закончился и перед хранилищем, которое объект, занятый, повторно используется или освобождается, любое значение gl, которое ссылается на оригинальный объект может использоваться, но только ограниченным образом. Для объекта под строительство или уничтожение, см. 12.7. В противном случае такое значение glvalue относится к выделенному хранилищу (3.7.4.2) и использует свойства glvalue, которые не зависят от его значения, четко определены. Программа имеет поведение undefined, если:

  • для такого glvalue применяется преобразование lvalue-to-rval (4.1),

  • glvalue используется для доступа к нестатическому элементу данных или вызова нестатической функции-члена из объект или

  • glvalue привязан к ссылке на виртуальный базовый класс (8.5.3) или

  • glvalue используется как операнд dynamic_cast (5.2.7) или как операнд typeid.

Итак, что означает доступ? Это было выяснено с помощью отчета о дефекте 1531, который определяет доступ как:

Доступ

для чтения или изменения значения объекта

Итак, fill обращается к нестатическому элементу данных и, следовательно, имеет поведение undefined.

Это также согласуется с разделом 12.7, в котором говорится:

[...] Чтобы сформировать указатель на (или доступ к значению) прямого нестатического элемента объекта obj, должно начаться построение obj и его уничтожение не будет завершено, в противном случае вычисление значения указателя (или доступа значение члена) приводит к поведению undefined.

Поскольку вы все равно используете копию, вы можете создать экземпляр Data внутри fill и инициализировать это. Вам не нужно проходить d.

Как указано T.C. важно четко указать детали, когда начинается жизненный цикл. Из раздела 3.8:

Время жизни объекта - это свойство времени выполнения объекта. считается, что объект имеет нетривиальную инициализацию, если он имеет класс или совокупный тип, и он или один из его членов инициализируется конструктор, отличный от тривиального конструктора по умолчанию. [ Заметка: инициализация тривиальным конструктором copy/move является нетривиальной инициализация. - end note] Время жизни объекта типа T начинается, когда:

  • сохраняется правильное выравнивание и размер для типа T, а

  • если объект имеет нетривиальную инициализацию, его инициализация завершена.

Инициализация нетривиальна, поскольку мы инициализируем с помощью конструктора копирования.

Ответ 2

Я не вижу проблемы. Доступ к неинициализированным целым членам действителен, потому что вы получаете доступ для записи. Чтение их приведет к UB.

Ответ 3

Я думаю, что он действителен (сумасшедший, но действительный).

Это было бы законным и логически приемлемым:

Data d ;

d = fill( d ) ;

и тот факт, что эта форма одинаков:

Data d = fill( d ) ;

Что касается логической структуры языка, то эти две версии эквивалентны.

Таким образом, это законно и логически корректно для языка.

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

Интересно, что g++ -Wall компилирует этот код без blurp.