Требование переопределенных виртуальных функций для вызова базовых реализаций - PullRequest
15 голосов
/ 03 января 2012

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

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

Ответы [ 6 ]

9 голосов
/ 03 января 2012

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

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

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

Типичный ответ - добавить комментарий

// Always call base class method
3 голосов
/ 04 января 2012

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

#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, некоторые методы
  • C ++Стандарт в основном гарантирует только наличие typeid, но не совсем то, как он работает (большинство вещей «зависит от компилятора»)

edit:

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

#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                                       /
3 голосов
/ 03 января 2012

Поместите специальный «скрытый» тип в базовый класс с помощью частного конструктора, используя friend, чтобы гарантировать, что только Base может его создать. Этот код на 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().

1 голос
/ 03 января 2012

В языке C ++ это не поддерживается, но, расширив комментарий 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 в конце каждого вызова. Вот живая демонстрация .

0 голосов
/ 06 апреля 2018

Для этого есть лёгкая проверка: https://reviews.llvm.org/rCTE329448 (сделано мной, поэтому вы можете задать мне любые вопросы по этому поводу)

0 голосов
/ 03 января 2012

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...