2009-11-12 11 views
13

que tiene la siguiente estructura de clases:herencia de interfaces en C++

class InterfaceA 
{ 
    virtual void methodA =0; 
} 

class ClassA : public InterfaceA 
{ 
    void methodA(); 
} 

class InterfaceB : public InterfaceA 
{ 
    virtual void methodB =0; 
} 

class ClassAB : public ClassA, public InterfaceB 
{ 
    void methodB(); 
} 

Ahora el siguiente código no es compilables:

int main() 
{ 
    InterfaceB* test = new ClassAB(); 
    test->methodA(); 
} 

El compilador dice que el método methodA() es virtual y no se han aplicado. Pensé que está implementado en ClassA (que implementa el InterfaceA). ¿Alguien sabe dónde está mi culpa?

Respuesta

19

Eso es porque usted tiene dos copias de InterfaceA. Vea esto para una explicación más grande: https://isocpp.org/wiki/faq/multiple-inheritance (su situación es similar a 'el temido diamante').

Necesita agregar la palabra clave virtual cuando hereda ClassA de InterfaceA. También debe agregar virtual cuando hereda InterfaceB de InterfaceA.

+0

Gracias por la aclaración de Laura. –

+0

Pensé que una vez que se declara una función 'virtual' es siempre virtual en toda la jerarquía de clases, ya sea que las clases derivadas usen' virtual' o no al definirlo – johnbakers

4

Este problema existe porque C++ realmente no tiene interfaces, solo clases virtuales puras con herencia múltiple. El compilador no sabe dónde encontrar la implementación de methodA() porque está implementado por una clase base diferente de ClassAB. Usted puede evitar esto mediante la implementación de methodA() en ClassAB() a llamar a la implementación base:

class ClassAB : public ClassA, public InterfaceB 
{ 
    void methodA() 
    { 
     ClassA::methodA(); 
    } 

    void methodB(); 
} 
+0

Si las reglas de C++ son similares a las de Java, el compilador aún no lo sabría porque el puntero se declaró como 'InterfaceB', que no tiene' methodA'. –

+0

mmyers: Eso es incorrecto tanto para C++ como para Java. InterfaceB hereda de InterfaceA donde se define methodA. – jmucchiello

+0

La interfaz B hereda de la interfaz A, por lo que debe tener el método A – stonemetal

2

Tienes un diamante temido aquí. InterfaceB y ClassA deben heredar virtualmente de InterfaceA De lo contrario, ClassAB tiene dos copias de MethodA, una de las cuales es puramente virtual. No deberías poder instanciar esta clase. E incluso si lo fuera, el compilador no podría decidir a qué método llamar.

9

La herencia virtual, que Laura sugirió, es, por supuesto, la solución del problema. Pero no termina teniendo solo una InterfaceA. También tiene "efectos secundarios", por ej. ver https://isocpp.org/wiki/faq/multiple-inheritance#mi-delegate-to-sister. Pero si te acostumbras, puede ser útil.

Si no desea que los efectos secundarios, es posible utilizar la plantilla:

struct InterfaceA 
{ 
    virtual void methodA() = 0; 
}; 

template<class IA> 
struct ClassA : public IA //IA is expected to extend InterfaceA 
{ 
    void methodA() { 5+1;} 
}; 

struct InterfaceB : public InterfaceA 
{ 
    virtual void methodB() = 0; 
}; 

struct ClassAB 
    : public ClassA<InterfaceB> 
{ 
    void methodB() {} 
}; 

int main() 
{ 
    InterfaceB* test = new ClassAB(); 
    test->methodA(); 
} 

Por lo tanto, estamos teniendo exactamente una clase padre.

Pero parece más feo cuando hay más de una clase "compartida" (InterfaceA es "compartida", porque está encima de "temido diamante", ver aquí https://isocpp.org/wiki/faq/multiple-inheritance según lo publicado por Laura). Ver ejemplo (lo que será, si se implementa claseA InterfaceC también):

struct InterfaceC 
{ 
    virtual void methodC() = 0; 
}; 

struct InterfaceD : public InterfaceC 
{ 
    virtual void methodD() = 0; 
}; 

template<class IA, class IC> 
struct ClassA 
    : public IA //IA is expected to extend InterfaceA 
    , public IC //IC is expected to extend InterfaceC 
{ 
    void methodA() { 5+1;} 
    void methodC() { 1+2; } 
}; 

struct InterfaceB : public InterfaceA 
{ 
    virtual void methodB() = 0; 
}; 

struct ClassAB 
    : public ClassA<InterfaceB, InterfaceC> //we had to modify existing ClassAB! 
{ 
    void methodB() {} 
}; 

struct ClassBD //new class, which needs ClassA to implement InterfaceD partially 
    : public ClassA<InterfaceB, InterfaceD> 
{ 
    void methodB() {} 
    void methodD() {} 
}; 

Lo malo, que se necesitaba para modificar ClassAB existente. Sin embargo, se puede escribir:

template<class IA, class IC = interfaceC> 
struct ClassA 

Entonces ClassAB permanece sin cambios:

struct ClassAB 
     : public ClassA<InterfaceB> 

Y tiene aplicación por defecto para el parámetro de plantilla IC.

Qué manera de usar es para que usted decida. Prefiero la plantilla, cuando es simple de entender.Es muy difícil entrar en el hábito, que B :: incrementAndPrint() y C :: incrementAndPrint() se imprimirán valores diferentes (no tu ejemplo), ver esto:

class A 
{ 
public: 
    void incrementAndPrint() { cout<<"A have "<<n<<endl; ++n; } 

    A() : n(0) {} 
private: 
    int n; 
}; 

class B 
    : public virtual A 
{}; 

class C 
    : public virtual A 
{}; 

class D 
    : public B 
    : public C 
{ 
public: 
    void printContents() 
    { 
    B::incrementAndPrint(); 
    C::incrementAndPrint(); 
    } 
}; 

int main() 
{ 
    D d; 
    d.printContents(); 
} 

Y la salida:

A have 0 
A have 1