Вызов перегруженного конструктора виртуального базового класса

Есть ли (практический) способ обходить нормальный (виртуальный) порядок вызова конструктора?

Пример:

class A
{
    const int i;

public:
    A()
      : i(0)
    { cout << "calling A()" << endl; }

    A(int p)
      : i(p)
    { cout << "calling A(int)" << endl; }
};

class B
    : public virtual A
{
public:
    B(int i)
      : A(i)
    { cout << "calling B(int)" << endl; }
};

class C
    : public B
{
public:
    C(int i)
      : A(i), B(i)
    { cout << "calling C(int)" << endl; }
};

class D
    : public C
{
public:
    D(int i)
      : /*A(i), */ C(i)
    { cout << "calling D(int)" << endl; }
};


int main()
{
    D d(42);
    return 0;
}

Вывод:

вызов A()
вызов B (int)
вызов C (int)
вызов D (int)

Я хочу иметь что-то вроде:

вызов A (int)
вызов B (int)
вызов C (int)
вызов D (int)


Как вы видите, существует виртуальное наследование, которое приводит к тому, что конструктор D вызывает конструктор A сначала, но поскольку параметр не указан, он вызывает A(). Там const int i, который нуждается в инициализации, поэтому у меня есть проблема.

Что я хотел бы сделать, так это скрыть детали наследования C, поэтому я ищу способ избежать вызова A (i) в D (и каждый производный) список инициализации конструктора. [edit] В этом конкретном случае я могу предположить, что существуют только не виртуальные однонаследованные дочерние классы C (как D - один). [/Править]

[править]

Виртуальные базовые классы инициализируются до инициализации любых не виртуальных базовых классов, поэтому только самый производный класс может инициализировать виртуальные базовые классы. - Джеймс Макнеллис

Точно так, я не, хочу, чтобы самый производный класс вызывал конструктор виртуального базового класса. [/править]

Рассмотрим следующую ситуацию (не представлен в примере кода выше):

  A
 / \
B0  B1
 \ /
  C
  |
  D  

Я понимаю, почему C должен вызывать ctor A (двусмысленность) при создании экземпляра C, но почему D должен вызывать его при создании D?

Ответ 1

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

Это потому, что вы говорите, что виртуальная база разделяется между всеми классами, которые происходят от нее для экземпляра объекта. Так как конструктор может быть вызван только один раз для данного instaniation объекта, вы должны явно вызвать конструктор в самом производном классе, потому что компилятор не знает, сколько классов разделяют виртуальную базу (перефразируя (вероятно, плохо) из The Язык программирования С++ 3-е издание, раздел 15.2.4.1). Это связано с тем, что компилятор будет начинаться с самого базового класса конструктора и работать с наиболее производным классом. Классы, которые наследуются от виртуального базового класса напрямую, по стандарту не будут вызывать конструктор виртуальных базовых классов, поэтому его нужно вызывать явно.

Ответ 2

Я понимаю, почему C должен вызвать ctor of A (двусмысленность), когда вы instanciate C, но почему D должен назовите это, когда инициируете D?

По той же причине, что и C. Это не проблема двусмысленности, это тот факт, что конструктор A должен быть вызван только один раз (поскольку это виртуальная база).

Если вы надеялись, что C может инициализировать конструктор, то что, если класс D должен был наследовать C и другой класс, который в конечном итоге наследует A?

Ответ 3

Таковы правила. Существуют правила для переопределения виртуальных функций и правил для построения виртуальных базовых подобъектов. Хотя оба они очень схожи концептуально, они следуют совершенно другим правилам, по одной причине: переопределение виртуальной функции является явным. Вызов конструктора является неявным для конструктора по умолчанию.

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

Но конструкторы виртуального базового класса всегда вызывают из самого производного класса и обычно в неявной форме не будут упоминать виртуальный базовый класс в ctor-init-list, поскольку большинство классов, предназначенных для использования в качестве виртуальной базы классы - это "чистые интерфейсы" без элементов данных и без инициализации пользователя.

Ответ 4

В Parashift С++ - faq-lite этот вопрос очерчен.