Вызов базового члена в конструкторе в множественном наследовании в С++

Предположим, что у меня есть эти два класса

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 наверняка, как я только что протестировал.