Требование переопределенных виртуальных функций для вызова базовых реализаций

В рамках иерархии классов С++ можно ли требовать, чтобы конкретная виртуальная функция всегда вызывала также реализацию базового класса? (Как путь цепи конструкторов?)

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

Есть ли какой-нибудь шаблон для принудительного применения этого параметра, так что компилятор будет вызывать ошибку, если переопределение не может привязать базу?

Итак, в

class CAA 
{
   virtual void OnEvent( CEvent *e ) { 
     // do base implementation stuff;
   }
}

class CBB : public CAA
{
   typedef CAA BaseClass;
   virtual void OnEvent( CEvent *e ) { 
       DoCustomCBBStuff();
       BaseClass::OnEvent( e ); // chain to base
   }
}

class CCC : public CBB
{
   typedef CBB BaseClass;
   virtual void OnEvent( CEvent *e ) { 
       Frobble();
       Glorp();
       BaseClass::OnEvent( e ); // chain to CBB which chains to CAA, etc
   }
}

class CDD : public CCC
{
   typedef CCC BaseClass;
   virtual void OnEvent( CEvent *e ) { 
       Meep();
       // oops! forgot to chain to base!
   }
}

Есть ли способ, какой-то шаблонный трюк или синтаксический трюк, чтобы сделать CDD более очевидной ошибкой?

Ответы

Ответ 1

То, как это делается, метод базового класса не является виртуальным и вызывает защищенный виртуальный метод.

Конечно, это только один уровень.

В вашей конкретной ситуации большая часть инфраструктуры может заставить ее работать, но она не стоит.

Типичным ответом является добавление комментария

// Always call base class method

Ответ 2

Поместите специальный "скрытый" тип в базовый класс с помощью частного конструктора, используя friend, чтобы обеспечить его создание только базой. Этот код на ideone.

Если существует несколько уровней, это, к сожалению, не гарантирует, что вызывается немедленный базовый класс. Следовательно, struct E : public D; может реализовать E::foo() с вызовом B:: foo, если вы предпочитаете вызов D::foo().

struct Base {
        struct Hidden {
                friend class Base;
                private:
                        Hidden() {}
        };
        virtual Hidden foo() {
                cout << "Base" << endl;
                return Hidden(); // this can create a Hidden
        }
};

struct D : public B {
        virtual Hidden foo() {
                cout << "D" << endl;
                // return Hidden(); // error as the constructor is private from here
                return B :: foo();
        }
};

Если вы попытались реализовать D:: foo() без возврата или с помощью return Hidden(), вы получите сообщение об ошибке. Единственный способ скомпилировать это - использовать return B :: foo().

Ответ 3

Следуя простому правилу для вывода через класс шаблона, возможно.

#include <iostream>

struct TEvent
{
};

struct Base {
    virtual void CallOnEvent(TEvent * e)
    {
        OnEvent(e);
    }
    virtual void OnEvent(TEvent * e)
    {
        std::cout << "Base::Event" << std::endl;
    }
    void CallUp(TEvent * e)
    {
    }

};

template <typename B>
struct TDerived : public B
{
    void CallUp( TEvent * e )
    {
        B::CallUp(e);
        B::OnEvent(e);
    }
    virtual void CallOnEvent( TEvent * e )
    {
        CallUp(e);
        this->OnEvent(e);
    }
};

struct Derived01 : public TDerived< Base >
{
    void OnEvent(TEvent * e)
    {
        std::cout << "Derived01::Event" << std::endl;
    }
};

struct Derived02 : public TDerived< Derived01 >
{
    void OnEvent(TEvent * e)
    {
        std::cout << "Derived02::Event" << std::endl;
    }
};

struct Derived03 : public TDerived< Derived02 >
{
    void OnEvent(TEvent * e)
    {
        std::cout << "Derived03::Event" << std::endl;
    }
};

struct Derived04 : public TDerived< Derived03 >
{
    void OnEvent(TEvent * e)
    {
        std::cout << "Derived04::Event" << std::endl;
    }
};


int main( void )
{
 Derived04 lD4;
 lD4.CallOnEvent(0);
 return 0;
}

Этот код дает (codepad):

Base::Event
Derived01::Event
Derived02::Event
Derived03::Event
Derived04::Event

Относительно некоторых ответов, используя typeid. Я бы никогда не подумал использовать typeid для чего угодно, кроме отладки. Это связано с двумя вещами:

  • проверка динамического типа может выполняться гораздо более эффективными способами (без создания объекта type_info, используя dynamic_cast, некоторые методы
  • Стандарт С++ в основном гарантирует только существование типа, но на самом деле не имеет ничего общего с тем, как он работает (большинство вещей "специфичны для компилятора" ).

изменить

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

#include <iostream>

struct TEvent
{
};

struct Base {
    virtual void CallOnEvent(TEvent * e)
    {
        OnEvent(e);
    }
    virtual void OnEvent(TEvent * e)
    {
        std::cout << "Base::Event" << std::endl;
    }

    void CallUp(TEvent * e)
    {
    }
};

template <typename B >
struct TDerived : public B
{
    void CallUp( TEvent * e )
    {
        B::CallUp(e);
        B::OnEvent(e);
    }
    virtual void CallOnEvent( TEvent * e )
    {
        CallUp(e);
        this->OnEvent(e);
    }
};

struct Derived01 : virtual public TDerived< Base >
{
    void OnEvent(TEvent * e)
    {
        std::cout << "Derived01::Event" << std::endl;
    }
};

struct Derived02 : virtual public TDerived< Derived01 >
{
    void OnEvent(TEvent * e)
    {
        std::cout << "Derived02::Event" << std::endl;
    }
};

typedef TDerived< Derived02 > TDerived02;
typedef TDerived< Derived01 > TDerived01;
struct Derived03 : virtual public TDerived02, virtual public TDerived01
{
    void OnEvent(TEvent * e)
    {
        std::cout << "Derived03::Event" << std::endl;
    }

    virtual void CallOnEvent( TEvent * e )
    {
        CallUp(e);
        Derived03::OnEvent(e);
    }
    void CallUp( TEvent * e )
    {
        TDerived02::CallUp(e);
        TDerived01::CallUp(e);
    }
};

struct Derived04 : public TDerived< Derived03 >
{
    void OnEvent(TEvent * e)
    {
        std::cout << "Derived04::Event" << std::endl;
    }
};


int main( void )
{
 Derived04 lD4;
 Derived03 lD3;

 lD3.CallOnEvent( 0 );
 std::cout << std::endl;
 lD4.CallOnEvent( 0 );

 return ( 0 );
}

Результат (ideone):

Base::Event      \                  \
Derived01::Event | - from Derived02 |
Derived02::Event /                  |-- from Derived03
Base::Event      \__ from Derived01 |
Derived01::Event /                  |
Derived03::Event                    /

Base::Event      \                  \                  \
Derived01::Event | - from Derived02 |                  |
Derived02::Event /                  |-- from Derived03 |-- from Derived04
Base::Event      \__ from Derived01 |                  |
Derived01::Event /                  |                  |
Derived03::Event                    /                  |
Derived04::Event                                       /

Ответ 4

Нет поддержки для этого на языке С++, но, добавив комментарий KerrekSB, вы можете сделать что-то вроде этого:

class A {
public:
    void DoEvent(int i) {
        for (auto event = events.begin(); event != events.end(); ++event)
            (this->*(*event))(i);
    }

protected:
    typedef void (A::*Event)(int);

    A(Event e) {
        events.push_back(&A::OnEvent);
        events.push_back(e);
    }

    void OnEvent(int i) {
        cout << "A::OnEvent " << i << endl;
    }

    vector<Event> events;
};

class B : public A {
public:
    B() : A((Event)&B::OnEvent) { }

protected:
    B(Event e) : A((Event)&B::OnEvent) {
        events.push_back(e);
    }

    void OnEvent(int i) {
        cout << "B::OnEvent " << i << endl;
    }
};

class C : public B {
public:
    C() : B((Event)&C::OnEvent) { }

protected:
    C(Event e) : B((Event)&C::OnEvent) {
        events.push_back(e);
    }

    void OnEvent(int i) {
        cout << "C::OnEvent " << i << endl;
    }
};

Затем используйте его так:

int main() {
    A* ba = new B;
    ba->DoEvent(32);

    B* bb = new B;
    bb->DoEvent(212);

    A* ca = new C;
    ca->DoEvent(44212);

    B* cb = new C;
    cb->DoEvent(2);

    C* cc = new C;
    cc->DoEvent(9);
}

Это выводит

A::OnEvent 32
B::OnEvent 32

A::OnEvent 212
B::OnEvent 212

A::OnEvent 44212
B::OnEvent 44212
C::OnEvent 44212

A::OnEvent 2
B::OnEvent 2
C::OnEvent 2

A::OnEvent 9
B::OnEvent 9
C::OnEvent 9

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

Ответ 5

Нет ничего прямого принуждения функции переопределения, чтобы делать что-либо в частности, за исключением возврата определенного типа. Однако, если вы создаете виртуальную функцию базового класса private, никакая функция не может вызвать ее в базовом классе, но производные классы могут ее переопределить. Затем вы также предоставляете функцию public, которая вызывает виртуальную функцию, а также функцию, выполняющую логику базового класса. Логика из базового класса, вероятно, должна перейти в отдельную функцию (возможно, не виртуальную функцию пересылки), чтобы избежать ее выполнения дважды, если объект фактически является базовым объектом.