Виртуальное наследование (diamond) - почему мне нужно передавать в базовый класс из самого производного класса


Рассмотрим следующее :

#include <iostream>
#include <string>
using namespace std;


class A {
public:
   A(const char* sName) //conversion constructor
           : _sName(sName) {cout<<"(1)"<<endl;} ;
   A(const A& s) {cout<<"(2)"<<endl;} //copy constructor
   virtual ~A() {cout<<"(3)"<<endl;} //destructor
    void f1() {cout<<"(4)"<<endl; f2();} //Notice two commands!
    virtual void f2() =0;
  private:
    string _sName;
  };



  class B1: virtual public A {
  public:
     B1(const char* sAName, const char* sSName)
             : _sName1(sAName), A(sSName) {cout<<"(5)"<<endl;}
     B1(const B1& b1) : A(b1) {cout<<"(6)"<<endl;}
     ~B1() {cout<<"(7)"<<endl;}
     virtual void f1() {cout<<"(8)"<<endl;}
     virtual void f2() {cout<<"(9)"<<endl; f3();}
     virtual void f3() {cout<<"(10)"<<endl;}
  private:
     string _sName1;
  };



  class B2: virtual public A {
  public:
     B2(const char* sAName, const char* sSName)
             : _sName2(sAName), A(sSName) {cout<<"(11)"<<endl;}
     B2(const B2& b2) : A(b2) {cout<<"(12)"<<endl;}
     ~B2() {cout<<"(13)"<<endl;}
     virtual void f3() {f1(); cout<<"(14)"<<endl;}
  private:
      string _sName2;
  };

  class C: public B1, public B2 {
  public:
         C () : A(" this is A ") , B1(" this is " , " B1 ") , B2 (" this is " , " B2 ") {}
         C (const C& c) :  A(c) , B1(c) , B2(c) {}
         ~C() {cout<<"(15)"<<endl;}
         virtual void f1() {A::f1(); cout<<"(16)"<<endl;}
         void f3 () {cout<<"(17)"<<endl;}
  };


  int main() {
      /* some code */
      return 0;
  }

Как вы можете видеть, я добавил в class C реализацию Ctor (конструктора) C . Что мне не ясно, так это зачем мне нужен также upcast от C до A, если B1 делает эту работу для меня в своем Ctor ? То есть, если бы я написал C'S Ctor как:

C () : A(" this is A ") , B1(" this is " , " B1 ") , B2 (" this is " , " B2 ") {}

Почему я не могу написать:

C () : B1(" this is " , " B1 ") , B2 (" this is " , " B2 ") {}

Спасибо , Ронен

5 2

5 ответов:

Конструктор виртуального базового называется до невиртуальная поколений конструктор по. В вашем примере В1 конструктор нельзя вызвать виртуальный базовый конструктор для C как и само Б1 конструктор будет вызван позже.

Короче говоря, потому что это то, что требует стандарт: вы должны инициализировать виртуальную базу в ctor самого derieved класса.

Более подробный ответ, это потому, что у вас есть только один базовый подобъект для виртуальных баз, и этот подобъект может быть инициализирован по-разному в разных базовых классах. Например, в вашем примере


C () : B1(" this is " , " B1 ") , B2 (" this is " , " B2 ") {}

Какое значение вы ожидаете передать ctor, " B1 " или "B2"?

Потому что " B1 " и " B2 "используют одну и ту же память для "A", что и "C". Если вы не указали конструкцию " A " в "C", то какой из " B1 " или " B2 "должен построить "A"?

Ваша терминология здесь немного ошибочна - это не апкаст на "А".

Потому что у вашего class A нет конструктора по умолчанию.

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

Следующий оператор кода:

C () : B1(" this is " , " B1 ") , B2 (" this is " , " B2 ") {}

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

Вот быстрая попытка:

#include <iostream>

class A { public: A(int v) : m_v(v) { std::cout << "Initializing A with " << v << std::endl; } int m_v; };
class B1 : public virtual A { public: B1(int v) : A(v) {} };
class B2 : public virtual A { public: B2(int v) : A(v) {} };
class C : public B1, public B2 { public: C(int v1, int v2, int v3) : A(v1), B1(v2), B2(v3) {} };

int main()
{
    C c(1, 2, 3);

    std::cout << "c.m_v: " << c.m_v << std::endl;

    return EXIT_SUCCESS;
}

Этот пример выводит:

Initializing A with 1
c.m_v: 1

То есть, кажется, что вызов A::A() необходим в самом производном классе, потому что, поскольку наследование virtual, он не будет вызываться конструкторами B1 или B2 при создании экземпляра C.