2015-09-02 15 views
10

Breve:Assicurarsi che classe derivata dalla classe genitore CRTP implementa la funzione

voglio per assicurarsi che una classe derivata implementa una funzione di membro richiesto da una funzione all'interno della classe genitore CRTP.

dettaglio:

ho qualche codice come questo

class Base 
{ 
public: 
    class Params 
    { 
    public: 
     virtual ~Params() {} 
    }; 

    virtual void myFunc(Params& p) = 0; 
}; 

template< typename T > 
class CRTP : public Base 
{ 
public: 
    virtual void myFunc(Base::Params& p) override 
    { 
     typename T::Params& typedParams = dynamic_cast<typename T::Params&>(p); 
     static_cast<T*>(this)->myFunc(typeParams); 
    } 

}; 

class Imp : public CRTP<Imp> 
{ 
public: 
    class Params : public CRTP<Imp>::Params 
    { 
    public: 
     virtual ~Params() {} 

     int x, y, z; 
    }; 

    virtual void myFunc(Imp::Params& p); 
}; 

L'intenzione è che posso avere più Imp classi figlie tutti facendo cose diverse in myFunc e accettando i propri parametri richiesti. L'interfaccia fornita da Base viene quindi utilizzata dalle funzioni di livello superiore che necessitano solo di un puntatore/riferimento di tipo Base::Params e Base. Il mio problema è assicurarsi che qualsiasi Imp fornisca un myFunc specializzato. Per evitare la ricorsione infinita, Imp deve implementare myFunc.

Il mio primo tentativo è stata aggiunta una funzione virtuale pura al CRTP

virtual void myFunc(typename T::Params& p) = 0; 

ma che non funziona come Imp non è stato completamente definito quando CRTP è in via di definizione. This question utilizza uno static_assert che mi ha fatto pensare di fare lo stesso con lo static_assert entro CRTP::myFunc. Tranne che non sono sicuro di quale debba essere l'espressione nell'asserzione statica per una funzione non statica.

  1. Posso utilizzare uno static_assert per quello che mi serve?
  2. È il modo migliore/più pulito per garantire che la classe derivata abbia la funzione necessaria?
  3. Sono stato portato via con il mio design di classe e c'è un modo migliore di fare le cose?

Grazie.

+0

non si può usare qualche magia SFINAE per stabilire se 'Imp :: Param' è diverso da' Base :: Param' e che 'Imp :: myFunc()' accetta 'Imp :: Param' come argomento? – Walter

+0

@Walter per il secondo, se c'è ereditarietà, avrà falsi positivi. Per il primo, potresti non volerlo richiedere. – Yakk

+1

A parte: perché stai colando static a 'T const *' da un metodo non-'const'? O 'myFunc' dovrebbe essere' const' in 'Base' e' CRTP', o dovresti chiamare 'static_cast ' nell'implementaiton 'CRTP'. – Yakk

risposta

5

Perché non utilizzare un nome diverso per la funzione? Quindi si avrà un errore di compilazione per ogni derivazione della classe CRTP senza e implementazione. Considerate questo:

class Base 
{ 
public: 
    class Params 
    { 
    public: 
     virtual ~Params() {} 
    }; 

    virtual void myFunc(Params& p) = 0; 
}; 

template< typename T > 
class CRTP : public Base 
{ 
public: 
    virtual void myFunc(Base::Params& p) final override 
    { 
     typename T::Params& typedParams = dynamic_cast<typename T::Params&>(p); 
     static_cast<const T*>(this)->myFuncImp(typedParams); 
    } 

}; 

class Imp : public CRTP<Imp> 
{ 
public: 
    class Params : public CRTP<Imp>::Params 
    { 
    public: 
     virtual ~Params() {} 

     int x, y, z; 
    }; 
}; 

int main(int argc, char** argv) 
{ 
    Imp imp; 
} 

compilazione non riesce dal momento che non v'è alcuna myFuncImp fornito da Imp.

+2

Aggiungi 'final' a' myFunc' in 'CRTP' per evitare confusione. – Yakk

+1

@Walter Potrei aver frainteso, ma se 'myFunc' chiama un' myFuncImp 'specializzato, come si differenzia? Una chiamata a 'Base :: myFunc' si ridurrà ancora al' CRTP <> :: myFuncImp' corretto? –

+0

@Yakk sul posto, fammi modificare quello. –

0

L'idea di utilizzare un nome diverso per il membro delle classi derivate (come nella risposta di Rudolfs Bundulis) è buona. Tuttavia, renderei questo un metodo protected in modo che gli utenti non siano tentati di provare a utilizzarlo.

Inoltre, utilizzando Derived::Params nella base CRTP solleva ulteriori complicazioni (dal momento che non è completamente Derived=Imp dichiarato al punto del suo utilizzo in CRTP<Imp>), quindi è meglio tenere Base::Params come il parametro della funzione in tutto.

struct Base           // public user interface 
{ 
    struct Params { virtual ~Params() {} }; 
    virtual void myFunc(Params&) = 0; 
}; 

namespace details {         // deter clients from direct access 
    template< typename Derived > 
    struct CRTP : Base 
    { 
    virtual void myFunc(Params& p) final   // cannot be overridden 
    { 
     static_cast<Derived*>(this)->myFuncImp(p); 
    } 
    }; 

    class Imp : public CRTP<Imp> 
    { 
    struct Params : CRTP<Imp>::Params { int x, y, z; }; 
    void myFuncImpDetails(Params*); 
    protected:           // protected from clients 
    void myFuncImp(Base::Params& p) 
    { 
     auto pars=dynamic_cast<Params*>(&p); 
     if(pars) 
     myFuncImpDetails(pars); 
     else 
     throw std::runtime_error("invalid parameter type provided to Imp::myFunc()"); 
    } 
    }; 
} // namespace details 
+0

"the unwieldy dynamic_cast <>" è piuttosto necessario in quanto non c'è modo di sapere se la p passata è del tipo corretto. Il punto di CRTP è di evitare la duplicazione della trasmissione dinamica nelle classi di Imp (multiple). – user2746401

+0

Oh, e il fatto che la funzione protetta (da sola) non funzioni come CRTP non ha accesso ai membri privati ​​/ protetti in Imp. Tuttavia, ho aggiunto CRTP come amico a Imp per ovviare a questo problema. – user2746401

1

è possibile interrompere il polimorfismo dinamico e passa a polimorfismo statico:

#include <iostream> 
#include <type_traits> 

class Base 
{ 
    public: 
    class Params 
    { 
     public: 
     virtual ~Params() {} 
    }; 

    virtual ~Base() {} 
    virtual void myFunc(Params& p) = 0; 
}; 


namespace Detail { 
    // Helper for the static assertion 
    // Omit this if "‘void CRTP<T>::myFunc(Base::Params&) [with T = Imp]’ is private" is good enough 
    struct is_MyFunc_callable_implementation 
    { 
     template<typename Object, typename Params> 
     static decltype(std::declval<Object>().myFunc(std::declval<Params&>()), std::true_type()) 
     test(int); 

     template<typename Object, typename Params> 
     static std::false_type 
     test(...); 
    }; 

    template<typename Object, typename... A> 
    using is_MyFunc_callable = decltype(is_MyFunc_callable_implementation::test<Object, A...>(0)); 

    // Helper function to break recursion 
    template<typename Object, typename Params> 
    inline void invokeMyFunc(Object& object, Params& params) { 
     static_assert(is_MyFunc_callable<Object, Params>::value, "The derived class is missing 'MyFunc'"); 
     object.myFunc(params); 
    } 
} // namespace Detail 

template<typename T> 
class CRTP: public Base 
{ 
    private: 
    // Make this final! 
    virtual void myFunc(Base::Params& p) override final 
    { 
     static_assert(std::is_base_of<Base, T>::value, "T must derive from CRTP"); 
     typename T::Params& typeParams = dynamic_cast<typename T::Params&>(p); 
     Detail::invokeMyFunc(static_cast<T&>(*this), typeParams); 
    } 
}; 

class Imp: public CRTP<Imp> 
{ 
    public: 
    class Params: public CRTP<Imp>::Params 
    { 
     public: 
     int x = 1; 
     int y = 2; 
     int z = 3; 
    }; 

    // Without this function: 
    // error: static assertion failed: The derived class is missing 'MyFunc' 
    // error: ‘void CRTP<T>::myFunc(Base::Params&) [with T = Imp]’ is private 
    #if 0 
    void myFunc(Params& p) { 
     std::cout << p.x << p.y << p.z << '\n'; 
    } 
    #endif 
}; 

int main() 
{ 
    Imp imp; 
    Base* base = &imp; 
    Imp::Params params; 
    base->myFunc(params); 
} 

Tuttavia, la mia opinione è: Il design classe base è un fallimento e il codice di cui sopra è solo un lavoro intorno.

+0

Potresti approfondire il "fallimento" del design della classe base? Come si potrebbe migliorare? – user2746401

+0

@ user2746401 Non immagino alcun caso d'uso per questo. Il più vicino è la gestione degli eventi, ma non dovrebbe passare al polimorfismo statico. –

Problemi correlati