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

У меня проблемы с множественным наследованием и проблемой алмазов.

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

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

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

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)
                 ^

person user2445507    schedule 17.07.2016    source источник


Ответы (3)


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

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

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

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

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

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

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

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

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

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

person Dietmar Kühl    schedule 17.07.2016
comment
На самом деле ничего бы не изменило введение чего-то, что предотвратило бы автоматическую генерацию конструктора по умолчанию. Все базы должны быть как-то инициализированы. Таким образом, последний абзац в порядке, но IMO цитата из стандартов не имеет значения. - person Cheers and hth. - Alf; 18.07.2016
comment
Спасибо за дополнительную цитату. По-видимому, этого нет в С++ 03, но более понятная версия есть в С++ 11. Хм, сегодня кое-что узнал, спасибо. :) - person Cheers and hth. - Alf; 18.07.2016
comment
О, вы имеете в виду, что из-за удаленного конструктора по умолчанию, например. ContractA код не должен компилироваться. Но из-за специальной обработки абстрактного класса определение конструктора по умолчанию по умолчанию тривиально, нет необходимости в инициализаторе члена для виртуальной базы. Это правильное резюме? - person Cheers and hth. - Alf; 18.07.2016
comment
@Cheersandhth.-Alf: да - текст в кавычках для удаленного конструктора по умолчанию, похоже, подразумевает, что конструктор по умолчанию будет удален, если он не определен явно. Другой текст, по-видимому, подразумевает, что для конструкторов по умолчанию абстрактных баз существует специальная обработка. Разработчики EDG и clang, как правило, лучше осведомлены об этих деталях, чем я, и оба компилятора принимают исходный код. Возможно, мои рассуждения слишком строги... - person Dietmar Kühl; 18.07.2016

К сожалению, язык C+03 не сделал абстрактные классы особым случаем. вызовы конструктора, и до сих пор, начиная с С++ 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();
}
person Cheers and hth. - Alf    schedule 17.07.2016
comment
Это очень плохо. В моем случае я просто дал базовому классу параметр по умолчанию. Это хорошо работает для меня здесь, потому что большинство объектов в любом случае будут иметь один и тот же параметр. Затем я могу перегрузить параметр, если это необходимо. Не очень хорошо, но фиктивный маршрут конструктора имеет каскадный эффект, который требует от меня написания большого количества бесполезного кода. - person user2445507; 18.07.2016
comment
@ user2445507: Возможно, вы можете использовать недопустимое значение по умолчанию и assert, что это не то значение. - person Cheers and hth. - Alf; 18.07.2016

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

person aschepler    schedule 17.07.2016