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

Инициализация объектов взаимной привязки

Рассмотрим следующую пару типов взаимной привязки:

struct A;
struct B { A& a; };
struct A { B& b; };

Это может быть инициализировано с помощью агрегатной инициализации в GCC, Clang, Intel, MSVC, но не SunPro, которая настаивает на том, что требуемые пользователем ctors необходимы.

struct {A first; B second;} pair = {pair.second, pair.first};

Является ли эта инициализация законной?

немного более сложная демонстрация: http://ideone.com/P4XFw

Теперь, прислушиваясь к предупреждению Sun, как насчет классов с пользовательскими конструкторами? Следующие работы в GCC, clang, Intel, SunPro и MSVC, но являются ли они законными?

struct A;
struct B { A& ref; B(A& a) : ref(a) {} };
struct A { B& ref; A(B& b) : ref(b) {} };

struct {B first; A second;} pair = {pair.second, pair.first};

demo: http://ideone.com/QQEpA

И, наконец, что, если контейнер не является тривиальным, например, (работает в g++, Intel, Clang (с предупреждениями), но не MSVC ( "пара" неизвестна в инициализаторе) или SunPro ( "пара не является структурой" )

std::pair<A, B> pair(pair.second, pair.first);

Из того, что я вижу, §3.8[basic.life]/6 запрещает доступ к нестатическому члену данных до начала жизни, но является lvalue-оценкой пары. второй "доступ" ко второму? Если это так, то все три инициализации незаконны? Кроме того, §8.3.2[dcl.ref]/5 говорит, что "ссылка должна быть инициализирована для ссылки на действительный объект", что, вероятно, также делает все три незаконными, но, возможно, мне что-то не хватает, и компиляторы принимают это по какой-то причине.

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

4b9b3361

Ответ 1

Сначала это меня исказило, но я думаю, что получил его сейчас. В соответствии с 12.6.2.5 стандарта 1998 года С++ гарантирует, что члены данных инициализируются в том порядке, в котором они объявлены в классе, и что тело конструктора выполняется после того, как все члены были инициализированы. Это означает, что выражение

struct A;
struct B { A& a; };
struct A { B& b; };
struct {A first; B second;} pair = {pair.second, pair.first};

имеет смысл, поскольку пара является переменной auto (local, stack), поэтому ее относительный адрес и адрес членов известны компилятору, а для первого и второго не существует конструкторов.

Почему два условия означают, что приведенный выше код имеет смысл: когда first, типа A, строится (перед любым другим элементом данных pair), элемент данных first b установлен на reference pair.second, адрес которого известен компилятору, потому что это переменная стека (пространство уже существует для нее в программе AFAIU). Обратите внимание, что pair.second как объект, т.е. Сегмент память, не был инициализирован (содержит мусор), но это не меняет того факта, что адрес этого мусора известен во время компиляции и может для установки ссылок. Поскольку A не имеет конструктора, он не может ничего предпринимать с помощью b, поэтому поведение корректно определено. После инициализации first это поворот second, и тот же: его член данных A ссылается pair.first, который имеет тип A и pair.first адрес известен компилятору.

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

Теперь для варианта:

struct A;
struct B { A& ref; B(A& a) : ref(a) {} };
struct A { B& ref; A(B& b) : ref(b) {} };
struct {B first; A second;} pair = {pair.second, pair.first};

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

ОДНАКО, если в корпусе конструктора B есть код, который получает ссылку на что-то (pair.second), которое еще не было инициализировано (но для какого адреса определено и известно), и этот код использует A, хорошо, что вы ищете неприятности. Если вам повезет, вы получите крах, но запись в A, скорее всего, завершится неудачно, так как значения будут позже перезаписаны при вызове конструктора A. of

Ответ 2

С точки зрения компилятора ссылки - это не что иное, как константные указатели. Перепишите свой пример указателями и выясните, как и почему он работает:

struct A;
struct B { A* a; };
struct A { B* b; };
struct {A first; B second;} pair = {&(pair.second), &(pair.first)}; //parentheses for clarity

Как писал Шоллий: память выделяется заранее, таким образом адресуемая. Нет доступа или оценки из-за ссылок/указателей. Это просто принятие адресов "второй" и "первой", простой арифметики указателей.

Я мог бы рассказать о том, как использование ссылок в любом месте, кроме оператора, является жестоким обращением с языком, но я думаю, что этот пример достаточно хорошо освещает проблему:)

(С этого момента я пишу все ctors вручную. Ваш компилятор может или не может сделать это автоматически для вас.) Попробуйте использовать новое:

struct A;
struct B { A& a; B(A& arg):a(arg){;} };
struct A { B& b; A(B& arg):b(arg){;} };
typedef struct PAIR{A first; B second; PAIR(B& argB, A& argA):first(argB),second(argA){;}} *PPAIR, *const CPPAIR;
PPAIR pPair = NULL;// just to clean garbage or 0xCDCD
pPair = new PAIR(pPair->second, pPair->first);

Теперь это зависит от порядка исполнения. Если назначение выполняется последним (после ctor), second.p будет указывать на 0x0000 и first.ref, например. 0x0004.
Фактически, http://codepad.org/yp911ug6 здесь это ctors, которые запускаются последними (имеет смысл!), Поэтому все работает (хотя кажется, что shouldn ' т).

Невозможно говорить о шаблонах.

Но ваш вопрос: "Это законно?". Ни один закон не запрещает это.
Это будет работать? Ну, я не доверяю разработчикам компилятора, чтобы делать какие-либо заявления об этом.