Вызов базового члена в конструкторе в множественном наследовании в С++
Предположим, что у меня есть эти два класса
class base_size
{
public:
int size()
{ return 5; }
};
class base_implement
{
public:
base_implement(int s) : _vec(s)
{
cout << "size : " << _vec.size() << endl;
}
private:
vector<float> _vec;
};
Если бы я должен был наследовать от обоих из них, было бы нормально вызвать одну из этих функций-членов класса в другом конструкторе? Например
class derived :
public base_implement,
public base_size
{
public:
derived() : base_size(), base_implement(size())
{
// Is this OK?
// If derived is not yet constructed can I access this->size() ?
// Works in VC++. Not sure about other compilers.
}
};
Ответы
Ответ 1
В принципе это прекрасно. Базовые подобъекты и объекты-члены создаются до того, как выполняется производное тело конструктора, поэтому вы можете вызвать функцию-член без проблем. Вы даже можете вызвать свои собственные функции-члены в конструкторе; вам просто нужно убедиться, что они не полагаются на то, что приходит позже в том же конструкторе.
Просто не забудьте вызвать базовые инициализаторы в правильном порядке, то есть порядок их объявления, и/или исправить определение вашего класса. Для base_size::size()
вы хотите, чтобы подобъект base_size
был полностью построен, поэтому он имеет чтобы прийти первым.
class derived : base_size, base_implement
{
derived() : base_size(), base_implement(size()) { /* ... */ }
// ...
};
Ответ 2
Вы можете безопасно вызвать унаследованную функцию-член из списка инициализации при условии, что ничто из того, что эта функция не зависит от того, как инициализированы данные элемента до этого уровня наследования. И поскольку size()
не полагается на какие-либо данные элемента, и все, что он делает, просто возвращает литерал
int size()
{ return 5; }
ваш код будет работать с любым компилятором. Таким образом, даже нет необходимости иметь base_size()
в списке инициализации
derived() : base_size(), base_implement(size())
в этом случае.
Однако переключение на более реалистичный пример, где base_size
имеет конструктор, который инициализирует переменную экземпляра (т.е. данные элемента), имеет смысл иметь base_size()
в списке инициализации:
class base_size
{
public:
base_size ()
{ _size = 5; } // initialization
int size()
{ return _size; }
private:
int _size; // instance variable
};
class base_implement
{
public:
base_implement(int s) : _vec(s)
{
cout << "size : " << _vec.size() << endl;
}
private:
vector<float> _vec;
};
class derived :
public base_implement,
public base_size
{
public:
derived() : base_size(), base_implement(size())
{
// crash
}
};
И в этом конкретном примере программа выйдет из строя, потому что vector
не получит действительное значение для распределения его размера. И причиной будет порядок базовых классов, которые у вас есть в так называемом базовом-спецификаторе-списке:
public base_implement,
public base_size
Ссылаясь на авторитет, это то, что указано standard в разделе 12.6.2, "Инициализация баз и членов"
Инициализация должна выполняться в следующем порядке:
- Во-первых, и только для конструктора самого производного класса, как описано ниже, виртуальные базовые классы должны быть инициализированы в том порядке, в каком они появляются на первом пересечении слева направо, направленном ациклическим графом базовых классов, где "слева направо" - это порядок появления имен базового класса в базе-спецификаторе производного класса.
- Затем прямые базовые классы должны быть инициализированы в порядке объявления, как они появляются в списке-спецификаторе-основы (независимо от порядка инициализаторов mem).
- Затем нестатические члены данных должны быть инициализированы в том порядке, в котором они были объявлены в определении класса (опять же независимо от порядка mem-инициализаторов).
- Наконец, выполняется тело конструктора.
Итак, если вы заменили
public base_implement,
public base_size
с
public base_size,
public base_implement
все будет правильно инициализироваться, и программа будет работать нормально с большинством совместимых со стандартом компиляторов. Во-первых, с MSVC 10.0 наверняка, как я только что протестировал.