C++: производный + базовый класс реализует единый интерфейс?

В С++ возможно ли, чтобы базовый плюс производный класс реализовывал один интерфейс?

Например:

class Interface
{
    public:
        virtual void BaseFunction() = 0;
        virtual void DerivedFunction() = 0;
};

class Base
{
    public:
        virtual void BaseFunction(){}
};

class Derived : public Base, public Interface
{
    public: 
        void DerivedFunction(){}
};

void main()
{
    Derived derived;
}

Это не удается, потому что Derived не может быть создан. Что касается компилятора, то Interface::BaseFunction никогда не определяется.

До сих пор единственным решением, которое я нашел, было бы объявить сквозную функцию в Derived.

class Derived : public Base, public Interface
{
    public: 
        void DerivedFunction(){}
        void BaseFunction(){ Base::BaseFunction(); }
};

Есть ли лучшее решение?


EDIT: Если это имеет значение, вот реальная проблема, с которой я столкнулся при использовании диалогов MFC.

У меня есть класс диалога (скажем, MyDialog), который является производным от CDialog. Из-за проблем с зависимостями мне нужно создать абстрактный интерфейс (MyDialogInterface). Класс, который использует MyDialogInterface, должен использовать методы, специфичные для MyDialog, но также должен вызывать CDialog::SetParent. Я просто решил это, создав MyDialog::SetParent и передав его CDialog::SetParent, но мне было интересно, есть ли лучший способ.


person Joe    schedule 14.11.2008    source источник


Ответы (5)


C++ не замечает, что функция, унаследованная от Base, уже реализует BaseFunction: функция должна быть явно реализована в классе, производном от Interface. Измените это следующим образом:

class Interface
{
    public:
        virtual void BaseFunction() = 0;
        virtual void DerivedFunction() = 0;
};

class Base : public Interface
{
    public:
        virtual void BaseFunction(){}
};

class Derived : public Base
{
    public: 
        virtual void DerivedFunction(){}
};

int main()
{
    Derived derived;
}

Если вы хотите реализовать только один из них, разделите Interface на два интерфейса:

class DerivedInterface
{
    public:
        virtual void DerivedFunction() = 0;
};

class BaseInterface
{
    public:
        virtual void BaseFunction() = 0;
};

class Base : public BaseInterface
{
    public:
        virtual void BaseFunction(){}
};

class Derived : public DerivedInterface
{
    public: 
        virtual void DerivedFunction(){}
};  

class Both : public DerivedInterface, public Base {
    public: 
        virtual void DerivedFunction(){}
};

int main()
{
    Derived derived;
    Base base;
    Both both;
}

Примечание: функция main должна возвращать значение int
Примечание: рекомендуется хранить virtual перед функциями-членами производной функции, которые были виртуальными в базе, даже если это не является строго обязательным.

person Johannes Schaub - litb    schedule 14.11.2008
comment
Если вы сделаете это таким образом, вам не понадобится второе объявление BaseFunction. - person Torlack; 14.11.2008
comment
Это то, что он написал. Я не хотел его менять. У него могут быть причины так поступать - person Johannes Schaub - litb; 14.11.2008
comment
Нет проблем, я просто не хотел, чтобы он или другие прошли через вечность, думая, что это необходимо. :) - person Torlack; 14.11.2008
comment
2 проблемы: - Теперь BAse не может быть создан сам по себе. - Другие классы, производные от базы, теперь должны реализовывать класс DerivedFunction OtherDerived : public Base { public: void OtherDerivedFunction(){} }; - person Joe; 14.11.2008
comment
Я отредактировал код. Я не хотел помещать второе объявление BaseFunction - person Joe; 14.11.2008
comment
Создайте два интерфейса, один для BaseFunction и один для DerivedFunction. - person Greg Rogers; 14.11.2008
comment
В вашем исходном примере вы все равно не могли создать экземпляр Base. Вторая проблема также связана с вашим исходным примером. - person Torlack; 14.11.2008
comment
Да, Джо. Тогда вам нужно сделать два интерфейса. Это не работает так, как в Java. - person Johannes Schaub - litb; 14.11.2008
comment
я добавил решение с двумя интерфейсами - person Johannes Schaub - litb; 14.11.2008
comment
Мне нравятся ваши ответы, но я думаю, что сквозная функция на самом деле проще всего. Ваш класс Both определяет свою собственную реализацию, а мне нужен способ использовать реализации в Base и Derived. Я понимаю, что вполне мог бы разрабатывать свои классы как дурак. :) - person Joe; 14.11.2008
comment
Во-вторых, я думаю, вы имели в виду класс Derived : public Base, public DerivedInterface - person Joe; 15.11.2008
comment
Я не это имел в виду. но если вы хотите, чтобы поведение, я действительно могу изменить его - person Johannes Schaub - litb; 15.11.2008
comment
@JohannesSchaub-litb: хорошей практикой является сохранение виртуального: его следует обновить, теперь у нас есть С++ 11, определяющий override. - person jpo38; 04.10.2019

Похоже, что это не совсем тот случай, когда Derived «является» Base, что предполагает, что сдерживание может быть лучшей реализацией, чем наследование.

Кроме того, ваши производные функции-члены также должны быть обозначены как виртуальные.

class Contained
{
    public:
        void containedFunction() {}
};

class Derived
{
    public:
        virtual void derivedFunction() {}
        virtual void containedFunction() {return contained.containedFunction();}
    private:
        Containted contained;
};

Вы можете сделать содержащийся элемент ссылкой или интеллектуальным указателем, если хотите скрыть детали реализации.

person JohnMcG    schedule 14.11.2008

Проблема в том, что в вашем примере у вас есть две реализации Interface, одна из Base, а другая из Derived. Это сделано на языке C++. Как уже было сказано, просто удалите базовый класс Interface в определении Derived.

person Torlack    schedule 14.11.2008
comment
гм.. нет.... Проверьте код еще раз. Base не наследуется от интерфейса. (Интерфейс имеет DerviceFunction, который не реализован в Base) - person James Curran; 14.11.2008

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

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

Derived
 vtable: Interface
   BaseFunction*
   DerivedFunction*
 vtable: Base
   BaseFunction*

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

Если бы предоставленный вами код мог скомпилироваться, результирующая структура vtable экземпляра Derived выглядела бы так:

Derived
 vtable: Interface
   BaseFunction* = 0
   DerivedFunction* = Derived::DerivedFunction
 vtable: Base
   BaseFunction* = Base::BaseFunction
person Kennet Belenky    schedule 14.11.2008

Я обнаружил, что в ответе litb не хватает одной вещи. Если у меня есть экземпляр Derived, я могу получить DerivedInterface и BaseInterface. Но если у меня есть только DerivedInterface, я не могу получить BaseInterface, так как получение DerivedInterface из BaseInterface не сработает.

Но все это время я по какой-то причине ограничивал себя проверкой времени компиляции. Этот DerivedInterface отлично работает:

class DerivedInterface
{
    public:
        virtual void DerivedFunction() = 0;
        BaseInterface* GetBaseInterface()
            {return dynamic_cast<BaseInterface*>(this);}
};

void main()
{
    Derived derived;

    DerivedInterface* derivedInterface = &derived;
    derivedInterface->GetBaseInterface()->BaseFunction();
}

В Derived нет необходимости проходить через функции, и все довольны. Конечно, это уже не строго интерфейс, но это нормально. Почему я не подумал об этом раньше? :)

person Joe    schedule 15.11.2008
comment
это не проблема. вы просто делаете класс производным от DerivedInterface и BaseInterface, и тогда у вас есть точно интерфейс вашего класса интерфейса в исходном вопросе - person Johannes Schaub - litb; 15.11.2008
comment
в любом случае. никогда не используйте dynamic_cast таким образом :) dynamic_cast должен быть последним средством для обхода того, что нельзя сделать, разрабатывая правильные интерфейсы. в вашем случае просто сделайте интерфейс, как я сказал в комментарии выше, а затем вы можете сделать BaseInterface* baseInterface = и вызвать оба :) - person Johannes Schaub - litb; 15.11.2008
comment
Да, это даст мне тот же интерфейс, но другую реализацию, в этом и весь смысл. У меня есть реализация для базового и производного классов, и мне нужен для них единый интерфейс. Похоже, это настолько близко, насколько я смогу. - person Joe; 15.11.2008
comment
подождите, я имел в виду вас как класс, который определяет свою собственную реализацию. Хммм. - person Joe; 15.11.2008