2012-01-03 12 views
19

Рассмотрим следующую пару взаимно ссылающихся типов:Инициализация взаимно ссылающихся объектов

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

Теперь, вняв предупреждение Солнца, что о классах с определенным пользователем конструкторами? Следующие работы в 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}; 

демо: 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: Я понимаю, что эти классы практически не практичны, следовательно, la nguage-lawyer tag. Связанные и незначительно более практические старые обсуждения здесь: Circular reference in C++ without pointers

+2

Я чувствую, что ваши совокупные конструкции верны (хотя я не могу это доказать сейчас), в то время как неагрегатная версия с 'std :: pair', конечно же, не допускается по причинам, которые вы заявляете. Вы можете задать более простой вопрос: 'struct Foo {Foo & r; Foo (Foo & f): r (f) {}} x (x);' –

+0

Можете ли вы заставить его работать с указателями? –

+0

Второй пример не является законным, насколько я могу судить, так как только агрегаты (которые могут не иметь конструкторы, объявленные пользователем) могут иметь инициализаторы-скобки: '§8.5 [dcl.init]/14' –

ответ

1

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

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

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

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

Если адреса не были известны компилятору (скажем, потому что, используя память кучи через новый оператор), должна произойти ошибка компиляции или, если нет, неопределенное поведение. Хотя разумное использование нового оператора размещения может позволить ему работать, поскольку с тех пор адреса 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. от

1

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

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

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

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

(Теперь я пишу все 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, которые выполняются последними (имеет смысл!), Поэтому все работает (хотя и кажется, что это не так).

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

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

 Смежные вопросы

  • Нет связанных вопросов^_^