2012-01-03 20 views
15

Dentro de una jerarquía de clases C++, ¿es posible imponer el requisito de que una función virtual particular siempre invoque también la implementación de su clase base? (Al igual que la cadena de constructores de forma?)Requerir funciones virtuales anuladas para llamar a implementaciones base

Estoy viendo un caso en el que una jerarquía de clases profundas tiene algunas funciones comunes de interfaz que cada niño anula. Me gustaría que cada anulación de clase derivada se encadenara a la clase base. Es sencillo hacerlo explícitamente con , por ejemplo,, el código siguiente, pero existe el riesgo de que alguien que implementa una nueva clase derivada se olvide de encadenarse a la base.

¿Hay algún patrón para imponer esto, de modo que el compilador arrojará un error si una anulación falla al encadenar la base?

Así, en

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! 
    } 
} 

hay una manera, algún truco truco plantilla o sintáctico, para hacer CDD lanzar un error más obvio?

+1

que yo sepa, pero siempre se puede dar a los baseclass una función no virtual y almacenar una lista de punteros a funciones, a los que cada clase derivada añade su parte. –

+0

Efectivamente, un duplicado de [Cómo forzar al hijo a la misma función virtual a llamar primero a su función virtual principal] (http://stackoverflow.com/questions/5644338/how-to-force-child-same-virtual-function-call-its -parent-virtual-function-first) –

+0

posible duplicado de [C++: ¿Llamar a un método de clase base automáticamente?] (http://stackoverflow.com/questions/3107974/c-call-a-base-class-method-automatically) – outis

Respuesta

9

La forma en que se hace es que el método de la clase base no es virtual y llama a un método virtual protegido.

Por supuesto, que solo maneja un nivel.

En su situación particular, una gran cantidad de infraestructura puede hacer que funcione, pero no vale la pena.

La respuesta típica es añadir un comentario

// Always call base class method 
+1

Ese fue un largo tiempo entre responder y aceptar. – Joshua

0

No hay nada aplicar directamente una función preponderante de hacer nada en particular, excepto devolver un cierto tipo. Sin embargo, si realiza la función virtual de la clase base private, ninguna función puede invocarla en la clase base, pero las clases derivadas pueden anularla. A continuación, también proporciona una función public que llama a la función virtual así como a una función que hace la lógica de clase base. La lógica de la clase base probablemente debería entrar en una función separada (posiblemente la función de reenvío no virtual directamente) para evitar ejecutarla dos veces si el objeto realmente es un objeto base.

+0

El problema con esto es que solo cubre un nivel, es decir, que todas las llamadas derivadas llamarán a la función base definitiva, pero todas las clases intermedias no participan en esa parte. – Xeo

+0

@Xeo: de acuerdo. No conozco ningún enfoque que refuerce el encadenamiento de funciones virtuales. Aunque considero que la idea de encadenar la función principal es linda (se usa, por ejemplo, en Xt, un sistema de GUI orientado a objetos para X11 implementado en C; todo lo relacionado con objetos debe hacerse manualmente), no necesitaba nada más que construcción y destrucción que están encadenadas. –

1

No hay apoyo para esto en el lenguaje C++, pero en expansión en el comentario de KerrekSB, que podría hacer algo como esto:

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; 
    } 
}; 

Entonces utilizar de esta manera

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); 
} 

que da salida a

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 

Tienes que trabajar un poco, pero no tienes que llamar manualmente al miembro más básico función al final de cada llamada. Here is the live demo.

+0

A lo que me refiero es como demasiado trabajo, pero funciona. – Joshua

+0

@Joshua, lamentablemente es lo mejor que puede hacer sin recurrir a la convención. –

3

Ponga un tipo especial 'oculto' en la clase Base con un constructor privado, usando friend para asegurarse de que solo la Base pueda crearlo. This code on ideone.

Si hay varios niveles, esto lamentablemente no garantiza que se llame a la clase base inmediata. Por lo tanto, struct E : public D; podría implementar E::foo() con una llamada al B:: foo cuando prefiera una llamada al 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(); 
     } 
}; 

Si se trató de poner en práctica D :: foo() sin un retorno o con return Hidden(), recibirá un mensaje de error. La única forma de compilar esto es usar return B :: foo().

+0

Cuty ... pero ligeramente defectuoso. 'foo()' debe devolver una referencia constante y debe evitar que se hagan copias de 'Hidden'. Por supuesto, su solución ya es buena para evitar omisiones accidentales, especialmente cambiar el nombre de 'Hidden' a' DoNotForgetToChain' o algo así. –

3

Siguiendo una regla simple para derivar a través de una clase de plantilla, es posible.

rendimientos
#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; 
} 

Este código (codepad):

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

con respecto a algunas respuestas usando typeid. Nunca consideraría usar typeid para nada más que la depuración. Esto es debido a dos cosas:

  • comprobación de tipo dinámico se puede hacer de una manera mucho más eficiente (sin crear type_info objeto es decir, utilizando dynamic_cast, algunos métodos
  • estándar
  • C++ básicamente sólo garantiza la existencia de typeid, pero no es realmente nada con respecto a cómo funciona (la mayoría de las cosas son "compilador específico")

edición:

Un poco más ejemplo complejo con herencia múltiple. Desafortunadamente, este no se puede resolver sin llamadas explícitas en clases que heredan de múltiples bases (principalmente porque no está claro lo que debería suceder en tales casos, por lo que tenemos que definir explícitamente el comportamiento).

#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); 
} 

El resultado es (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          /
No
Cuestiones relacionadas