2

У меня возникли проблемы с множественным наследованием и проблемой с алмазом.Вам нужно вызвать конструктор виртуального базового класса из всех производных классов? Даже если они не самые производные?

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

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

Вот код, который воспроизводит то, что я говорю:

class VirtualBase 
{ 

     public: 

       VirtualBase(int initial) : 
         count(initial) 
     {} 

       int getCount() const 
       { 
         return count; 
       } 

       void increment() 
       { 
         count++; 
       } 

     private: 

       int count; 

}; 


class ContractA : public virtual VirtualBase 
{ 

     public: 

       virtual void doSomething() = 0; 

}; 

class ContractB : public virtual VirtualBase 
{ 

     public: 

       virtual void doSomethingElse() = 0; 

}; 

class Concrete : public ContractA, public ContractB 
{ 

     public: 

       Concrete() : 
         VirtualBase(0) 
     {} 

       virtual void doSomething() 
       { 
         increment(); 
       } 

       virtual void doSomethingElse() 
       { 
         // etc...  
       } 

}; 

int main() 
{ 

     Concrete concrete; 
     concrete.doSomething(); 
     concrete.doSomethingElse(); 
     return 0; 
} 

Я получаю следующее сообщение об ошибке (для каждого контракта):

main.cpp: In constructor ‘ContractA::ContractA()’: 
main.cpp:29:7: error: no matching function for call to ‘VirtualBase::VirtualBase()’ 
class ContractA : public virtual VirtualBase 
    ^
main.cpp:29:7: note: candidates are: 
main.cpp:9:3: note: VirtualBase::VirtualBase(int) 
    VirtualBase(int initial) : 
^
main.cpp:9:3: note: candidate expects 1 argument, 0 provided 
main.cpp:4:7: note: VirtualBase::VirtualBase(const VirtualBase&) 
class VirtualBase 
    ^
main.cpp:4:7: note: candidate expects 1 argument, 0 provided 
main.cpp: In constructor ‘Concrete::Concrete()’: 
main.cpp:53:17: note: synthesized method ‘ContractA::ContractA()’ first required here 
    VirtualBase(0) 
       ^

ответ

3

К сожалению, C + 03 язык не сделать абстрактные классы специальным случаем по. конструктора, а еще с C++ 11 и более поздних версий компилятор g ++ (моя версия 5.1.0) не считает их особенными для этого.

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

Если вы хотите, чтобы понять читателю исходного кода, что эти фиктивные вызовы, то вы можете сделать это с помощью комментариев, или лучше, выражается непосредственно в исходном коде с assert:

#include <assert.h> 

struct Dummy_call {}; 

class VirtualBase 
{ 
private: 
    int count; 

protected: 
    VirtualBase(Dummy_call) { assert(false); } 

public: 
    auto getCount() const -> int { 
     return count; 
    } 
    void increment() { 
     ++count; 
    } 

    VirtualBase(int const initial) 
     : count(initial) 
    {} 
}; 

class ContractA 
    : public virtual VirtualBase 
{ 
public: 
    virtual void doSomething() = 0; 
    ContractA(): VirtualBase(Dummy_call{}) {} 
}; 

class ContractB 
    : public virtual VirtualBase 
{ 
public: 
    virtual void doSomethingElse() = 0; 
    ContractB(): VirtualBase(Dummy_call{}) {} 
}; 

class Concrete 
    : public ContractA 
    , public ContractB 
{ 
public: 
    void doSomething() override { 
     increment(); 
    } 
    void doSomethingElse() override {} // etc... 

    Concrete() 
     : VirtualBase{ 0 } 
    {} 
}; 

int main() 
{ 
    Concrete concrete; 
    concrete.doSomething(); 
    concrete.doSomethingElse(); 
} 
+0

Это очень плохо. В моем случае я просто дал базовому классу параметр по умолчанию. Это хорошо работает для меня здесь, потому что большинство объектов будут иметь одинаковый параметр в любом случае. Затем я могу перегрузить параметр, если это необходимо. Не очень, но маршрут «манекен-конструктор» имеет каскадный эффект, который требует от меня написать много бесполезного кода. – user2445507

+0

@ user2445507: Возможно, вы можете использовать «недопустимое» значение по умолчанию, а 'assert' - это не такое значение. –

4

Ваш пример компилируется с EDG и clang, но он не компилируется с gcc. Я не уверен, что код должен компилироваться так, как будто конструктор абстрактного базового класса объявлен как удаленный: согласно 12.1 [class.ctor], абзац 4, шестой маркер, по умолчанию конструктор по умолчанию объявляется как удаленный, если любой субобъект не имеет конструктор по умолчанию:

... A дефолте конструктор по умолчанию для класса X определяется как удален, если: ...

  • ...
  • любой потенциально построенный подобъект, за исключением нестатического элемента данных с привязкой или эквалайзером, имеет тип класса M (или его массив), а либо M не имеет конструктора по умолчанию или разрешения перегрузки (13.3), применяемого к конструктору по умолчанию M, приводит к неопределенности или функции, которая удалена или недоступна по умолчанию по умолчанию, или
  • ...

Там нет специального исключения для классов с virtual базисов по созданию virtual база, то есть дефолте конструктор по умолчанию будет удален.

Для абстрактных классов, по-видимому, необязательно вызывать виртуальную базу из списка инициализаторов элементов конструктора. По крайней мере, это то, что 12.6.2 [class.base.INIT] пункт 8 говорит, в соответствии с его запиской:

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

  • , если объект не является статическим членом данных, который имеет фигурную скобку или равно-инициализатор и либо
  • класс конструктора является объединением (9.5), и ни один другой вариант этого объединения не обозначается идентификатором mem-initializer-id или
  • класс конструктора не является объединением, и если объект является членом анонимного объединения, ни один член этого союза не назначается идентификатором mem-initializer, объект инициализируется, как указано в 8.5;
  • В противном случае, если объект является анонимным объединением или вариантом (9.5), инициализация не выполняется;
  • В противном случае объект инициализируется по умолчанию (8.5).

[Примечание: абстрактный класс (10.4) никогда не является самым производным классом, поэтому его конструкторы никогда не инициализируют виртуальные базовые классы, поэтому соответствующие mem-инициализаторы могут быть опущены. - конец примечание] ...

Соответствующая часть для самой производной основы в пункте 7 12.6.2, последнее предложение:

... A MEM-инициализатор где мем-инициализатор -id означает, что виртуальный базовый класс игнорируется во время выполнения конструктора любого класса, который не является самым производным классом.

+0

На самом деле ничего не изменит, чтобы представить что-то, что предотвратило автоматическое создание конструктора по умолчанию. Все базы должны быть инициализированы каким-то образом. Таким образом, последний абзац в порядке, но IMO - стандартная цитата. –

+0

Спасибо за дополнительную цитату. По-видимому, этого нет в C++ 03, но более ясная версия есть в C++ 11. Хм, узнал что-то сегодня, спасибо. :) –

+0

О, вы имеете в виду, что из-за удаленного конструктора по умолчанию, например. 'ContractA', код не должен компилироваться. Но из-за специальной трактовки абстрактного класса определение дефолтного конструктора по умолчанию тривиально, нет необходимости в инициализаторе элемента для виртуальной базы. Это правильное резюме? –

1

Ваш код хорошо сформирован с C++ 11. К сожалению, вы видите ошибку gcc 53878.