2010-01-18 25 views
5

Utilizzo le pseudointerfacce in C++, ovvero classi astratte pure. Supponiamo che io abbia tre interfacce, IFoo, IBar e IQuux. Ho anche una classe di Fred che implementa tutti e tre:Controllo dell'implementazione dell'interfaccia in fase di compilazione in C++

interface IFoo 
{ 
    void foo (void); 
} 

interface IBar 
{ 
    void bar (void); 
} 

interface IQuux 
{ 
    void quux (void); 
} 

class Fred : implements IFoo, IBar, IQuux 
{ 
} 

voglio dichiarare un metodo che accetta qualsiasi oggetto che implementa IFoo e IBar - un Fred avrebbe funzionato, per esempio. L'unico modo di compilazione tempo per fare questo posso immaginare è quello di definire una terza IFooAndBar interfaccia che implementa entrambi, e ridichiarare Fred:

interface IFooAndBar : extends IFoo, IBar 
{ 
} 

class Fred : implements IFooAndBar, IQuux 
{ 
} 

ora posso dichiarare il mio metodo come la ricezione di un IFooAndBar *. Fin qui tutto bene.


Tuttavia, che cosa succede se voglio anche un metodo diverso che accetta IBar e IQuux? Ho provato dichiara una nuova interfaccia IBarAndQuux e dichiarando Fred come ereditare entrambi:

class IFooAndBar : IFoo, IBar 
{ 
}; 


class IBarAndQuux : IBar, IQuux 
{ 
}; 


class Fred : IFooAndBar, IBarAndQuux 
{ 
}; 

Questo funziona quando passo Fred come IFooAndBar ad un metodo; tuttavia, quando provo a chiamare Fred :: bar() direttamente, gcc si lamenta:

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

che rende questa soluzione più o meno inutile.


Il mio prossimo tentativo è stato quello di dichiarare Fred come eredita da tre singole interfacce, e rendendo il metodo accetta una delle interfacce ibride come parametro:

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

}; 

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

Quando provo a passare Fred come IBarAndBaz * parametro, ottengo un errore, come previsto:

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

dynamic_cast <> anche p roduces un errore (che non capisco)

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

Forzare un cast fa lavoro, tuttavia:

doTest((IBarAndBaz*)pFred); 

ma mi chiedo come sicuro e portatile questo è (I sviluppare per Linux, Mac e Windows) e se funziona in una situazione reale.


Infine, mi rendo conto mio metodo può accettare un puntatore a una delle interfacce e dynamic_cast per l'altro (s) per far rispettare il tipo di parametro corretto in fase di esecuzione, ma preferisco una soluzione in fase di compilazione.

+2

Non è sicuro e non è portatile. In effetti, non è definito in C++. –

risposta

6

Considerare l'utilizzo di soluzioni testate prima - Boost.TypeTraits in soccorso:

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

C'è un mondo al di fuori di boost :-) – mmmmmmmm

+0

Naturalmente, bcp aiuta con quello;) Ma seriamente, perché duplicare una raccolta così completa di soluzioni per il compilatore, in particolare per i caratteri tipografici e simili? –

+1

Non penso che sia una buona idea estrarre la grande pistola boost se il normale C++ (ereditarietà virtuale) può risolvere il problema. – mmmmmmmm

1

È possibile ottenere l'effetto utilizzando il modello metaprogrammazione:

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

si comporteranno correttamente per le classi che forniscono bar() e baz(), e non riescono a compilare per qualsiasi altra classe.

+0

È molto interessante. Il mio caso reale è più complesso, in realtà: ho una classe che riceve l'oggetto che deve implementare entrambe le interfacce, mantiene un puntatore e quindi chiama i suoi metodi. Non mi sento a mio agio nell'implementare quella grande classe come modello :( – ggambett

+0

È possibile codificare un'asserzione in fase di compilazione che un tipo fornisce una particolare funzione membro senza richiamare quella funzione. Puoi farlo in un wrapper basato su modello che richiama quindi funzione reale, il codice basato sul modello viene dichiarato con l'attributo inline in modo che venga compilato. Tuttavia, l'implementazione è pelosa - è più semplice utilizzare quella da boost (vedere la risposta di gf) che rollare la propria – moonshadow

3

di farlo in stile OO, è necessario l'ereditarietà virtuale per garantire che Fred termina solo con una copia di 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 

Come altri hanno detto, si può fare qualcosa di simile al secondo tentativo con modelli per ottenere il polimorfismo statico.

Il cast che stavi cercando non è possibile, come un'istanza di Fred non è un'istanza di IBarAndBaz. Il cast forzato compila perché la maggior parte dei cast forzati si compilano, indipendentemente dal fatto che la conversione sia sicura, ma in questo caso darà un comportamento indefinito.

Edit: In alternativa, se non si desidera utilizzare i modelli e non ti piace l'esplosione combinatorical di definire tutti i possibili gruppi di interfacce, è possibile definire le funzioni di prendere ogni interfaccia come un parametro a parte:

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

class Fred : public IBar, public IBaz {}; 

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

Che potrebbe funzionare. che dovrei dichiarare tutte le combinazioni possibili di interfacce (almeno quelle che uso) nella definizione di Fred ... sarebbe molto meglio se potesse essere risolto nella posizione del metodo utente, non nella posizione del parametro, come il le soluzioni basate su modelli fanno. – ggambett

1

che cosa si può fare è creare una classe con un costruttore su modelli che accetta un puntatore arbitraria, usa downcasting implicita per ottenere le due interfacce che si desidera, e quindi implementa l'interfaccia combinata.

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

Poi si scrive una funzione che accetta IFooAndBar * se sono necessarie entrambe le interfacce, e il chiamante può costruire una FooAndBarCompositor sullo stack che invia l'oggetto della loro scelta. Sembra che:

void testFooAndBar(IFooAndBar* pI) {} 

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

Questo non è molto generale, e ti costringe a scrivere le funzioni di invio nel compositor. Un altro approccio è quello di avere un generico modello interfaccia compositor:

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

Allora la funzione è simile:

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

Questo richiede la funzione che vuole far rispettare le interfacce multiple usare sia per il compositore, dove un A * o B * è previsto (es. parametro di assegnazione o funzione) o chiamare esplicitamente il metodo AsX() appropriato. Nello specifico, l'interfaccia da utilizzare non può essere desunta dall'uso dell'operatore -> e l'operatore * non ha alcun significato sul composito.

Se si utilizza il codice generico, è possibile utilizzare lo stesso modello per far rispettare l'oggetto che supporta sia IBar che IBaz.

C++ 0x introdurrà i modelli variadic che consentiranno di estendere questo concetto a numeri arbitrari di classi di interfaccia.

Problemi correlati