2010-01-05 20 views
42

Hay dos clases base que tienen el mismo nombre de función. Quiero heredar ambos, y sobre montar cada método de manera diferente. ¿Cómo puedo hacer eso con una declaración y definición por separado (en lugar de definir en la definición de la clase)?Heredar interfaces que comparten un nombre de método

#include <cstdio> 

class Interface1{ 
public: 
    virtual void Name() = 0; 
}; 

class Interface2 
{ 
public: 
    virtual void Name() = 0; 
}; 

class RealClass: public Interface1, public Interface2 
{ 
public: 
    virtual void Interface1::Name() 
    { 
     printf("Interface1 OK?\n"); 
    } 
    virtual void Interface2::Name() 
    { 
     printf("Interface2 OK?\n"); 
    } 
}; 

int main() 
{ 
    Interface1 *p = new RealClass(); 
    p->Name(); 
    Interface2 *q = reinterpret_cast<RealClass*>(p); 
    q->Name(); 
} 

no pude mover la definición en VC8. He encontrado la palabra clave específica __interface Microsoft puede hacer este trabajo con éxito, código de abajo:

#include <cstdio> 

__interface Interface1{ 
    virtual void Name() = 0; 
}; 

__interface Interface2 
{ 
    virtual void Name() = 0; 
}; 

class RealClass: public Interface1, 
       public Interface2 
{ 
public: 
    virtual void Interface1::Name(); 
    virtual void Interface2::Name(); 
}; 

void RealClass::Interface1::Name() 
{ 
    printf("Interface1 OK?\n"); 
} 

void RealClass::Interface2::Name() 
{ 
    printf("Interface2 OK?\n"); 
} 

int main() 
{ 
    Interface1 *p = new RealClass(); 
    p->Name(); 
    Interface2 *q = reinterpret_cast<RealClass*>(p); 
    q->Name(); 
} 

pero ¿hay otra manera de hacerlo algo más general que funcionará en otros compiladores?

+0

¿Por qué quiere esto? Parece que estás derrotando el propósito de las funciones virtuales; simplemente estás replicando la semántica no virtual. ¿Tiene una referencia para '__interface'? Lo busqué y no parece hacer nada esencial. – Potatoswatter

+0

Agregué el __interface. – Gohan

+1

Potatoswatter: la razón por la que podría necesitar hacer esto es porque podría tener una clase que reúna dos conceptos diferentes heredando desde dos interfaces. Los usuarios de la clase probablemente lo utilicen a través de las interfaces y los usuarios de las dos interfaces diferentes no se conozcan ni se preocupen entre sí. La clase los combina y necesita diferenciar entre llamadas en una interfaz y llamadas en la otra. De ninguna manera estás derrotando el propósito de las funciones virtuales. Gohan - Me sorprendería si __inteface hiciera una diferencia aquí. –

Respuesta

56

Este problema no aparece muy a menudo. La solución que estoy familiarizado fue diseñado por Doug McIlroy y aparece en los libros de Bjarne Stroustrup (presentada en tanto Diseño & Evolución de C++ sección 12.8 y El C++ Programming Language sección 25.6).De acuerdo con la discusión en Diseño & Evolution, hubo una propuesta para manejar este caso específico elegantemente, pero fue rechazada porque "tales enfrentamientos de nombres no eran lo suficientemente comunes como para justificar una función de idioma separada" y "no es probable que convertirse en trabajo diario para principiantes ".

No sólo se necesita llamar a través de punteros Name() basar clases, necesita una manera de decir lo que Name() desea cuando se opera en la clase derivada. La solución agrega alguna indirección:

class Interface1{ 
public: 
    virtual void Name() = 0; 
}; 

class Interface2{ 
public: 
    virtual void Name() = 0; 
}; 

class Interface1_helper : public Interface1{ 
public: 
    virtual void I1_Name() = 0; 
    void Name() override 
    { 
     I1_Name(); 
    } 
}; 

class Interface2_helper : public Interface2{ 
public: 
    virtual void I2_Name() = 0; 
    void Name() override 
    { 
     I2_Name(); 
    } 
}; 

class RealClass: public Interface1_helper, public Interface2_helper{ 
public: 
    void I1_Name() override 
    { 
     printf("Interface1 OK?\n"); 
    } 
    void I2_Name() override 
    { 
     printf("Interface2 OK?\n"); 
    } 
}; 

int main() 
{ 
    RealClass rc; 
    Interface1* i1 = &rc; 
    Interface2* i2 = &rc; 
    i1->Name(); 
    i2->Name(); 
    rc.I1_Name(); 
    rc.I2_Name(); 
} 

No es bonito, pero la decisión fue que no es necesario a menudo.

+3

Tu respuesta es simple y suficiente, gracias ~ – Gohan

+2

Piensas y escribes muy rápido; v). +1 – Potatoswatter

+0

Es maravilloso que haya proporcionado las fuentes de la propuesta, aunque no estoy seguro de por qué no incluyó el contenido de la propuesta en la respuesta real, como un "hecho interesante" al menos, aunque brevemente. Para llenarme, tuve que buscar el libro en línea. Bastante una lectura interesante. Para lectores futuros: la propuesta rechazada introdujo la siguiente sintaxis para "cambiar el nombre" de un método de una interfaz implementada: 'virtual void Name1() = Interface1 :: Name;' Cheers – Tomalla

3

no se pueden sustituir por separado, debe invalidar ambas a la vez:

struct Interface1 { 
    virtual void Name() = 0; 
}; 

struct Interface2 { 
    virtual void Name() = 0; 
}; 

struct RealClass : Interface1, Interface2 { 
    virtual void Name(); 
}; 
// and move it out of the class definition just like any other method: 
void RealClass::Name() { 
    printf("Interface1 OK?\n"); 
    printf("Interface2 OK?\n"); 
} 

puede simular primordial individuo con clases base intermedios:

struct RealClass1 : Interface1 { 
    virtual void Name() { 
    printf("Interface1 OK?\n"); 
    } 
}; 

struct RealClass2 : Interface2 { 
    virtual void Name() { 
    printf("Interface2 OK?\n"); 
    } 
}; 

struct RealClass : RealClass1, RealClass2 { 
    virtual void Name() { 
    // you must still decide what to do here, which is likely calling both: 
    RealClass1::Name(); 
    RealClass2::Name(); 

    // or doing something else entirely 

    // but note: this is the function which will be called in all cases 
    // of *virtual dispatch* (for instances of this class), as it is the 
    // final overrider, the above separate definition is merely 
    // code-organization convenience 
    } 
}; 

Además, está utilizando reinterpret_cast de forma incorrecta, usted debe tener:

int main() { 
    RealClass rc; // no need for dynamic allocation in this example 

    Interface1& one = rc; 
    one.Name(); 

    Interface2& two = dynamic_cast<Interface2&>(one); 
    two.Name(); 

    return 0; 
} 

Y aquí es una reescritura con CRTP que podría ser lo que quiere (o no):

template<class Derived> 
struct RealClass1 : Interface1 { 
#define self (*static_cast<Derived*>(this)) 
    virtual void Name() { 
    printf("Interface1 for %s\n", self.name.c_str()); 
    } 
#undef self 
}; 

template<class Derived> 
struct RealClass2 : Interface2 { 
#define self (*static_cast<Derived*>(this)) 
    virtual void Name() { 
    printf("Interface2 for %s\n", self.name.c_str()); 
    } 
#undef self 
}; 

struct RealClass : RealClass1<RealClass>, RealClass2<RealClass> { 
    std::string name; 
    RealClass() : name("real code would have members you need to access") {} 
}; 

Pero en cuenta que aquí no se puede llamar nombre en una RealClass ahora (con despacho virtual, por ejemplo, rc.Name()), primero debe seleccionar una base. El auto macro es una manera fácil de limpiar los moldes de CRTP (por lo general, el acceso de los miembros es mucho más común en la base de CRTP), pero puede ser improved. Hay una breve discusión sobre el envío virtual en uno de mis other answers, pero seguramente será mejor si alguien tiene un enlace.

+1

Sus clases base intermedias no parecen hacer nada. Todavía puede llamar 'Interface1 :: Name' exactamente igual que' RealClass1 :: Name'. – Potatoswatter

+0

... Quiero decir, podrías, si se implementó como para RealClass1. – Potatoswatter

+0

No puede llamar a 'rc.Interface1 :: Name()', que es una función virtual pura sin implementación. –

5

He tenido que hacer algo como esto en el pasado, aunque en mi caso necesitaba heredar de una interfaz dos veces y poder diferenciar entre llamadas realizadas en cada una de ellas, utilicé una plantilla para ayudarme ...

Algo como esto:

template<class id> 
class InterfaceHelper : public MyInterface 
{ 
    public : 

     virtual void Name() 
     { 
      Name(id); 
     } 

     virtual void Name(
      const size_t id) = 0; 
} 

a continuación, se derivan de InterfaceHelper dos veces más que de MyInterface dos veces y se especifica un diferente id para cada clase base. A continuación, puede distribuir dos interfaces de forma independiente mediante conversión al InterfaceHelper correcto.

Podría hacer algo un poco más complejo;

class InterfaceHelperBase 
{ 
    public : 

     virtual void Name(
      const size_t id) = 0; 
} 


class InterfaceHelper1 : public MyInterface, protected InterfaceHelperBase 
{ 
    public : 

     using InterfaceHelperBase::Name; 

     virtual void Name() 
     { 
      Name(1); 
     } 
} 

class InterfaceHelper2 : public MyInterface, protected InterfaceHelperBase 
{ 
    public : 

     using InterfaceHelperBase::Name; 

     virtual void Name() 
     { 
      Name(2); 
     } 
} 

class MyClass : public InterfaceHelper1, public InterfaceHelper2 
{ 
    public : 

     virtual void Name(
      const size_t id) 
     { 
      if (id == 1) 
      { 
       printf("Interface 1 OK?"); 
      } 
      else if (id == 2) 
      { 
       printf("Interface 2 OK?"); 
      } 
     } 
} 

Tenga en cuenta que lo anterior no ha visto un compilador ...

+0

No es necesario usar un compilador para ver que 'Name (2);' no puede ser ** una llamada recursiva ** ya que esta función 'Name' no toma ningún argumento. – curiousguy

+0

No es una llamada recursiva. Las implementaciones reales de Name() llaman a una versión que incluye un Id para que pueda decir a qué versión de Name() se llamó en el objeto base. A continuación, reparte los objetos de ayuda a quien necesite la interfaz real. –

+0

Pero tal como está escrito, solo puede ser una llamada recursiva. Podría utilizar el operador de resolución de alcance para llamar a otra función 'Nombre', por ejemplo. – curiousguy

0

Hay otras dos cuestiones relacionadas pidiendo casi (pero no completamente) cosas idénticas:

Picking from inherited shared method names. Si desea tener rc.name() llame ic1-> name() o ic2-> name().

Overriding shared method names from (templated) base classes. Esto tiene una sintaxis más simple y menos código que la solución aceptada, pero no permite acceder a las funciones de la clase derivada. Más o menos, a menos que necesite poder llamar a name_i1() desde un rc, no necesita usar cosas como InterfaceHelper.

1
class BaseX 
{ 
public: 
    virtual void fun() 
    { 
     cout << "BaseX::fun\n"; 
    } 
}; 

class BaseY 
{ 
public: 
    virtual void fun() 
    { 
     cout << "BaseY::fun\n"; 
    } 
}; 


class DerivedX : protected BaseX 
{ 
public: 
    virtual void funX() 
    { 
     BaseX::fun(); 
    } 
}; 

class DerivedY : protected BaseY 
{ 
public: 
    virtual void funY() 
    { 
     BaseY::fun(); 
    } 
}; 


class DerivedXY : public DerivedX, public DerivedY 
{ 

}; 
Cuestiones relacionadas