2010-01-18 13 views
5

Estoy usando pseudo-interfaces en C++, es decir, clases abstractas puras. Supongamos que tengo tres interfaces, IFoo, IBar e IQuux. También tengo una clase que implementa Fred los tres de ellos:Comprobación de la implementación de la interfaz en tiempo de compilación en C++

interface IFoo 
{ 
    void foo (void); 
} 

interface IBar 
{ 
    void bar (void); 
} 

interface IQuux 
{ 
    void quux (void); 
} 

class Fred : implements IFoo, IBar, IQuux 
{ 
} 

Quiero declarar un método que acepta cualquier objeto que implemente IFoo y IBar - un Fred quiere trabajar, por ejemplo. La única forma de tiempo de compilación para hacer esto que puedo imaginar es definir una tercera IFooAndBar interfaz que implementa ambos, y redeclare Fred:

interface IFooAndBar : extends IFoo, IBar 
{ 
} 

class Fred : implements IFooAndBar, IQuux 
{ 
} 

Ahora puedo declarar mi método como la recepción de un IFooAndBar *. Hasta aquí todo bien.


Sin embargo, ¿qué ocurre si quiero también un método diferente que acepte Ibar y IQuux? He intentado declarar una nueva interfaz IBarAndQuux y declarando como Fred heredar tanto:

class IFooAndBar : IFoo, IBar 
{ 
}; 


class IBarAndQuux : IBar, IQuux 
{ 
}; 


class Fred : IFooAndBar, IBarAndQuux 
{ 
}; 

Esto funciona cuando paso Fred como IFooAndBar a un método; Sin embargo, cuando trato de llamar a Fred :: bar() directamente, gcc se queja:

error: request for member ‘bar’ is ambiguous 
error: candidates are: void IBar::bar() 
error:     void IBar::bar() 

lo que hace que esta solución más o menos inútil.


Mi siguiente intento fue declarar Fred como la herencia de las tres interfaces individuales, y hacer que el método de aceptar una de las interfaces híbridos como un parámetro:

class Fred : public IFoo, public IBar, public IBaz 
{ 

}; 

void doTest (IBarAndBaz* pObj) 
{ 
    pObj->bar(); 
    pObj->baz(); 
} 

Cuando trato de pasar Fred como el IBarAndBaz parámetro *, me sale un error, como se esperaba:

error: cannot convert ‘Fred*’ to ‘IBarAndBaz*’ for argument ‘1’ to ‘void doTest(IBarAndBaz*)’ 

dynamic_cast <> también p roduces un error (que no entiendo)

error: cannot dynamic_cast ‘pFred’ (of type ‘class Fred*’) to type ‘class IBarAndBaz*’ (source type is not polymorphic) 

Forzar a un elenco qué trabajo, sin embargo:

doTest((IBarAndBaz*)pFred); 

pero me pregunto qué tan seguro y portátil que es (que desarrollo para Linux, Mac y Windows), y si funciona en una situación del mundo real.


Por último, se dan cuenta de mi método puede aceptar un puntero a una de las interfaces y dynamic_cast al otro (s) para hacer cumplir el tipo de parámetro correcto en tiempo de ejecución, pero yo prefiero una solución en tiempo de compilación.

+2

No es seguro y es portable. De hecho, no está definido en C++. –

Respuesta

6

Considere el uso de soluciones ensayadas primero - Boost.TypeTraits al rescate:

template<class T> 
void takeFooAndBar(const T& t) { 
    BOOST_STATIC_ASSERT(
      boost::is_base_of<IFoo, T>::value 
     && boost::is_base_of<IBar, T>::value); 
    /* ... */ 
} 
+1

Hay un mundo fuera de impulso :-) – mmmmmmmm

+0

Por supuesto, bcp ayuda con eso;) Pero en serio, ¿por qué duplicar una colección tan completa de soluciones compiladoras, especialmente para rasgos de tipo y similares? –

+1

No creo que sea una buena idea sacar la pistola de refuerzo si C++ normal (herencia virtual) puede resolver el problema. – mmmmmmmm

1

Puede conseguir el efecto utilizando la plantilla metaprogramming:

tempate<class C> 
void doTest(C* pObj) 
{ 
    pObj->bar(); 
    pObj->baz(); 
} 

se comportará correctamente para las clases que suministran bar() y Baz(), y no compilar para ninguna otra clase.

+0

Eso es muy interesante. En realidad, mi caso de la vida real es más complejo: tengo una clase que recibe el objeto que necesita implementar ambas interfaces, mantiene un puntero al mismo y luego llama a sus métodos. No me siento cómodo implementando esa gran clase como una plantilla :( – ggambett

+0

Es posible codificar una afirmación en tiempo de compilación de que un tipo proporciona una función de miembro en particular sin invocar esa función. Podría hacerlo en un contenedor de plantillas que luego invoca el función real, el código de plantilla se declara con el atributo en línea para que se compile lejos. Sin embargo, la implementación es peliaguda: es más fácil usar la de boost (ver la respuesta de gf) que la propia. – moonshadow

3

Para hacer esto en el estilo orientado a objetos, es necesario herencia virtual para asegurar que Fred sólo logra que una copia de IBar:

class IFooAndBar : public IFoo, public virtual IBar {}; 
class IBarAndQuux : public virtual IBar, public IQuux {}; 

class Fred : public IFooAndBar, public IBarAndQuux {}; 

Fred fred; 
fred.bar(); // unambiguous due to virtual inheritence 

Como otros han dicho, se puede hacer algo similar a su segundo intento de utilizar plantillas para obtener polimorfismo estático.

El molde que estaba intentando no es posible, ya que una instancia de Fred no es una instancia de IBarAndBaz. Las compilaciones forzadas se compilarán porque la mayoría de las versiones forzadas se compilarán, independientemente de si la conversión es segura o no, pero en este caso dará un comportamiento indefinido.

Editar: Alternativamente, si no desea utilizar plantillas y no le gusta la explosión combinatoria de definir todos los posibles grupos de interfaces, puede definir las funciones para tomar cada interfaz como un parámetro separado:

void doTest(IBar *bar, IBaz *baz) 
{ 
    bar->bar(); 
    baz->baz(); 
} 

class Fred : public IBar, public IBaz {}; 

Fred fred; 
doTest(&fred,&fred); 
+0

Eso podría funcionar. que tendría que declarar todas las combinaciones posibles de interfaces (al menos las que uso) en la definición de Fred ... sería mucho mejor si se pudiera resolver en la ubicación del método de usuario, no en la ubicación del parámetro, como las soluciones basadas en plantillas sí. – ggambett

1

lo que puede hacer es crear una clase con un constructor de plantilla que acepta un puntero arbitraria, utiliza downcasting implícita para obtener las dos interfaces que desea, y luego implementa la interfaz combinada.

struct IFoo 
{ 
    virtual void foo() = 0; 
}; 

struct IBar 
{ 
    virtual void bar() = 0; 
}; 

struct IFooAndBar : public IFoo, public IBar {}; 

class FooAndBarCompositor : public IFooAndBar 
{ 
public: 
    template <class T> 
    FooAndBarCompositor(T* pImpl) : m_pFoo(pImpl), m_pBar(pImpl) {} 

    void foo() {m_pFoo->foo();} 
    void bar() {m_pBar->bar();} 

private: 
    IFoo* m_pFoo; 
    IBar* m_pBar; 
}; 

A continuación, se escribe una función que acepta IFooAndBar * si se requieren ambas interfaces, y la persona que llama puede construir una FooAndBarCompositor en la pila que se envía al objeto de su elección. Parece que:

void testFooAndBar(IFooAndBar* pI) {} 

void baz(Fred* pFred) 
{ 
    FooAndBarCompositor fb(pFred); 
    testFooAndBar(&fb); 
} 

Esto no es muy general, y obliga a escribir funciones de despacho en el compositor. Otro enfoque es tener una plantilla de interfaz compositor genérica:

template <class IA, class IB> 
class InterfaceCompositor 
{ 
public: 
    template <class T> 
    InterfaceCompositor(T* pObj) : m_pIA(pObj), m_pIB(pObj) {} 

    IA* AsA() const {return m_pIA;} 
    operator IA*() const {return AsA();} 
    IB* AsB() cosnt {return m_pIB;} 
    operator IB*() const {return AsB();} 

private: 
    IA* m_pIA; 
    IB* m_pIB; 
}; 

Entonces, la función se parece a:

void testFooAndBar(InterfaceCompositor<IFoo, IBar> pI) 
{ 
    IFoo* pFoo = pI; // Or pI.AsA(); 
    IBar* pBar = pI; // Of pI.AsB(); 
} 

Esto requiere la función que quiere que se cumplan las múltiples interfaces para cualquiera que use el compositor, donde una A * o B * es esperado (p. ej. asignación o parámetro de función) o explícitamente llame al método AsX() apropiado. Específicamente, la interfaz a usar no se puede deducir del uso del operador -> y el operador * no tiene ningún significado en el compuesto.

Si va con el código genérico, puede usar la misma plantilla para hacer que el objeto sea compatible con IBar e IBaz también.

C++ 0x introducirá plantillas variadic que permitirán que este concepto se extienda a números arbitrarios de clases de interfaz.

Cuestiones relacionadas