2013-02-21 15 views
6

Ho un'interfaccia oggetto e una raccolta di interfacce aperte che un oggetto derivato potrebbe voler supportare.Eredità multipla di interfacce in C++

// An object 
class IObject 
{ 
    getAttribute() = 0 
} 

// A mutable object 
class IMutable 
{ 
    setAttribute() = 0 
} 

// A lockable object 
class ILockable 
{ 
    lock() = 0 
} 

// A certifiable object 
class ICertifiable 
{ 
    setCertification() = 0 
    getCertification() = 0 
} 

Alcuni oggetti derivati ​​potrebbero apparire così:

class Object1 : public IObject, public IMutable, public ILockable {} 
class Object2 : public IObject, public ILockable, public ICertifiable {} 
class Object3 : public IObject {} 

Ecco la mia domanda: C'è un modo per scrivere funzioni che vorranno solo alcune combinazioni di queste interfacce? Ad esempio:

void doSomething(magic_interface_combiner<IObject, IMutable, ILockable> object); 

doSomething(Object1()) // OK, all interfaces are available. 
doSomething(Object2()) // Compilation Failure, missing IMutable. 
doSomething(Object3()) // Compilation Failure, missing IMutable and ILockable. 

La cosa più vicina che ho trovato è boost :: mpl :: inherit. Ho avuto un successo limitato ma non fa esattamente quello di cui ho bisogno.

Ad esempio:

class Object1 : public boost::mpl::inherit<IObject, IMutable, ILockable>::type 
class Object2 : public boost::mpl::inherit<IObject, ILockable, ICertifiable>::type 
class Object3 : public IObject 

void doSomething(boost::mpl::inherit<IObject, ILockable>::type object); 

doSomething(Object1()) // Fails even though Object1 derives from IObject and ILockable. 
doSomething(Object2()) // Fails even though Object2 derives from IObject and ILockable. 

penso che qualcosa di simile a boost :: :: MPL ereditano ma che avrebbe generato un albero di eredità con tutte le possibili permutazioni del tipo in dotazione potrebbero funzionare.

Sono anche curioso di altri approcci per risolvere questo problema. Idealmente qualcosa che compila i controlli del tempo rispetto al runtime (cioè nessun dynamic_cast).

+2

Intendi combinazioni AND, o combinazioni OR? –

+1

Correggetemi se ho torto, ma le vostre funzioni astratte non devono essere contrassegnate come 'virtuali' ed essere' = 0'? – ApproachingDarknessFish

+2

I take it use templates invece è la risposta sbagliata? –

risposta

1

Forse non è il modo più elegante, dal momento che è fatto con C++ 03 sintassi

template <typename T, typename TInterface> 
void interface_checker(T& t) 
{ 
    TInterface& tIfClassImplementsInterface = static_cast<TInterface&>(t); 
} 

Questo è quello di dare allo spirito del trucco. Ora nel tuo caso:

template <typename T, typename TInterface1, typename TInterface2, typename TInterface3 > 
void magic_interface_combiner(T& t) 
{ 
    TInterface1& tIfClassImplementsInterface = static_cast<TInterface1&>(t); 
    TInterface2& tIfClassImplementsInterface = static_cast<TInterface2&>(t); 
    TInterface3& tIfClassImplementsInterface = static_cast<TInterface3&>(t); 
} 

Credo che può essere fatto in modo più intelligente utilizzando C++ 11 caratteri morfologici.

2

Si dovrebbe usare static_assert per controllare i tipi all'interno della funzione:

#include <type_traits> 

template< typename T > 
void doSomething(const T& t) 
{ 
    static_assert(std::is_base_of<IObject,T>::value, "T does not satisfy IObject"); 
    static_assert(std::is_base_of<IMutable,T>::value, "T does not satisfy IMutable"); 

    // ... 
} 

che vi darà i messaggi di errore molto bello che ti dice che le interfacce non sono soddisfatti. Se avete bisogno di sovraccaricare la funzione e hanno una versione che è disponibile solo per una certa combinazione di interfaccia, è possibile utilizzare anche enable_if:

#include <type_traits> 

template< typename T, typename... Is > 
struct HasInterfaces; 

template< typename T > 
struct HasInterfaces<T> : std::true_type {}; 

template< typename T, typename I, typename... Is > 
struct HasInterfaces< T, I, Is... > 
    : std::integral_constant< bool, 
     std::is_base_of< I, T >::value && HasInterfaces< T, Is... >::value > {}; 

template< typename T > 
typename std::enable_if< HasInterfaces< T, IObject, IMutable >::value >::type 
doSomething(const T& t) 
{ 
    // ... 
} 

che renderà la funzione scompaiono dal set di sovraccarico quando i requisiti di interfaccia non sono soddisfatti

+0

'static_assert' è disponibile in linguaggio pre-C++ 11? –

+0

Sì: [BOOST_STATIC_ASSERT_MSG (v, msg)] (http://www.boost.org/doc/libs/1_53_0/doc/html/boost_staticassert.html) –

3

È possibile scrivere una classe di controllo di interfaccia con ricorsiva eredità variadic:

template<typename... Interfaces> 
struct check_interfaces; 
template<> 
struct check_interfaces<> { 
    template<typename T> check_interfaces(T *) {} 
}; 
template<typename Interface, typename... Interfaces> 
struct check_interfaces<Interface, Interfaces...>: 
public check_interfaces<Interfaces...> { 
    template<typename T> check_interfaces(T *t): 
     check_interfaces<Interfaces...>(t), i(t) {} 
    Interface *i; 
    operator Interface *() const { return i; } 
}; 

Ad esempio:

struct IObject { virtual int getAttribute() = 0; }; 
struct IMutable { virtual void setAttribute(int) = 0; }; 
struct ILockable { virtual void lock() = 0; }; 

void f(check_interfaces<IObject, IMutable> o) { 
    static_cast<IObject *>(o)->getAttribute(); 
    static_cast<IMutable *>(o)->setAttribute(99); 
} 

struct MutableObject: IObject, IMutable { 
    int getAttribute() { return 0; } 
    void setAttribute(int) {} 
}; 

struct LockableObject: IObject, ILockable { 
    int getAttribute() { return 0; } 
    void lock() {} 
}; 

int main() { 
    f(new MutableObject); 
    f(new LockableObject); // fails 
} 

Nota che check_interfaces ha un ingombro di un puntatore per ogni interfaccia controllato; questo perché esegue la cancellazione dei tipi sul tipo dichiarato dell'argomento effettivo.

+0

+1 per modelli variadici. –

1

solo per darvi un piccolo assaggio di C++ 11:

tipo singola senza ricorsione:

template <typename... Ts> 
class magic_interface_combiner { 
    typedef std::tuple<Ts*...> Tpl; 
    Tpl tpl; 

    template <typename T, int I> 
    T *as_(std::false_type) 
    { 
    static_assert(I < std::tuple_size<Tpl>::value, "T not found"); 
    return as_<T, I+1>(std::is_same<T, typename std::tuple_element<I+1, Tpl>::type>{}); 
    } 
    template <typename T, int I> 
    T *as_(std::true_type) { return std::get<I>(tpl); } 

public: 
    template <typename T> 
    magic_interface_combiner(T * t) : tpl(static_cast<Ts*>(t)...) {} 

    template <typename T> T * as() { return as_<T, 0>(std::false_type{}); } 
}; 

// no template  
void doSomething(magic_interface_combiner<IObject, IMutable, ILockable> object) 
{ 
} 

due tipi, ma senza ricorsione:

template <typename T> 
class single_interface_combiner { 
    T *p; 
public: 
    single_interface_combiner(T *t) : p(t) {} 
    operator T*() { return p; } 
}; 

template <typename... Ts> 
struct magic_interface_combiner : single_interface_combiner<Ts>... { 
    template <typename T> 
    magic_interface_combiner(T* t) : single_interface_combiner<Ts>(t)... {} 

    template <typename T> 
    T * as() { return *this; } 
}; 
2

Una soluzione che utilizza std::enable_if e std::is_base_of:

#include <type_traits> 

// An object 
struct IObject 
{ 
    virtual void getAttribute() = 0; 
}; 

// A mutable object 
struct IMutable 
{ 
    virtual void setAttribute() = 0; 
}; 

// A lockable object 
struct ILockable 
{ 
    virtual void lock() = 0; 
}; 

// A certifiable object 
struct ICertifiable 
{ 
    virtual void setCertification() = 0; 
    virtual void getCertification() = 0; 
}; 

struct Object1 : public IObject, public IMutable, public ILockable 
{ 
    void getAttribute() {} 
    void setAttribute() {} 
    void lock() {} 
}; 

struct Object2 : public IObject, public ILockable, public ICertifiable 
{ 
    void getAttribute() {} 
    void lock() {} 
    void setCertification() {} 
    void getCertification() {} 
}; 

struct Object3 : public IObject 
{ 
    void getAttribute() {} 
}; 

template<typename T> 
void doSomething(
    typename std::enable_if< 
     std::is_base_of<IObject, T>::value && 
     std::is_base_of<IMutable, T>::value && 
     std::is_base_of<ILockable, T>::value, 
     T>::type& obj) 
{ 
} 

int main() 
{ 
    Object1 object1; 
    Object2 object2; 
    Object3 object3; 

    doSomething<Object1>(object1); // Works 
    doSomething<Object2>(object2); // Compilation error 
    doSomething<Object3>(object3); // Compilation error 
}