список инициализации конструктора вопрос / вопрос распределения - PullRequest
5 голосов
/ 05 ноября 2010

Мой вопрос прост, следующий код безопасен?

struct Parent {
    B* _a;
    Parent(B* a) : _a(a) {}
};

struct Child : public Parent {
    B _b;
    Child() : Parent(&_b),  _b(2){};
};

int main() {
    Child c;
    return 0;
}

Еще два очка:

  • Мне интересна часть передачи ссылки на объект-член родителю.
  • Под безопасным я подразумеваю, что будет выделено _b (и его адрес памяти) и этот код будет работать независимо от того, какой компилятор я использую.

Заранее спасибо.

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

другие заметки
В моем реальном коде я хотел сохранить объект типа B как указатель на его базовый класс A, например:

struct Parent {
    A* _a;
    Parent(A* a) : _a(a) {}
};

struct Child : public Parent {
    B _b;
    Child() : Parent(&_b),  _b(2){};
};

int main() {
    Child c;
    return 0;
}

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

Ответы [ 3 ]

8 голосов
/ 05 ноября 2010

В том смысле, что вы описываете, да, это безопасно: память распределяется, и вполне нормально передать этот указатель родителю. Память для Child::_b фактически является неотъемлемой частью памяти всего Child. Это не требует каких-либо явных дополнительных «распределений». К моменту вызова конструктора Child::Child память, очевидно, уже там.

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

Но если вы, например, захотите преобразовать этот указатель в некоторый тип базового класса (если на секунду B имеет некоторый базовый класс), ваш код будет недопустимым. Недопустимо преобразовывать указатели в неинициализированные объекты в указатели их базового класса (опять же, см. 3.8).

6 голосов
/ 05 ноября 2010

Порядок инициализации следующий:

// Base classes:
Parent(&_b)

// Members:
_b(2)

// Constructor Body:
Child() { }

Является ли это безопасным, зависит от вашего определения "безопасно". По вашему определению («это будет работать?»), Да, это безопасно. Время жизни Child::_b начинается с момента создания объекта Child, поэтому вы можете получить указатель на него, и этот указатель ссылается на объект. Однако вы не можете использовать указанное значение до тех пор, пока не инициализируется _b, то есть после возврата конструктора базового класса, Parent.

1 голос
/ 05 ноября 2010

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

edit:

Я думаю, что AndreyT сформулировал более ясно и более наглядно тип проблем, которые возникли, когда я писал свой ответ.Это типы ошибок, которые не сразу воспринимаются.Тот тип, который будет держать вас ночью, пытаясь выяснить, где в вашем коде есть свисающий указатель или где происходит повреждение памяти.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...