2015-09-02 7 views
10

Brief:Stellen Sie sicher, dass die Klasse von übergeordneten CRTP Klasse abgeleitet implementiert Funktion

ich eine abgeleitete Klasse sicherstellen möchten, dass durch eine Funktion innerhalb des übergeordneten CRTP Klasse erforderlich, um eine Member-Funktion implementiert.

Detail:

Ich habe einige Code wie dieses

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

Die Absicht ist, dass ich mehrere Imp Kind Klassen alle verschiedene Dinge tun in myFunc und akzeptieren ihre eigenen erforderlichen Parameter haben kann. Die von Base bereitgestellte Schnittstelle wird dann von Funktionen auf höherer Ebene verwendet, die nur einen Zeiger/eine Referenz des Typs Base::Params und Base benötigen. Mein Problem ist sicherzustellen, dass alle Imp bietet eine spezialisierte myFunc. Um eine unendliche Rekursion zu vermeiden, muss ImpmyFunc implementiert werden.

Mein erster Versuch wurde das Hinzufügen einer rein virtuelle Funktion CRTP

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

aber das funktioniert nicht wie Imp nicht vollständig definiert, wenn CRTP definiert wird. This question verwendet eine static_assert, die mich dazu gebracht hat, das gleiche mit der static_assert innerhalb CRTP::myFunc zu tun. Nur bin ich nicht sicher, was der Ausdruck in der statischen Behauptung für eine nicht statische Funktion sein soll.

  1. Kann ich ein static_assert für das verwenden, was ich brauche?
  2. Ist das der beste/sauberste Weg, um sicherzustellen, dass die abgeleitete Klasse die benötigte Funktion hat?
  3. Habe ich mich von meinem Klassenentwurf mitreißen lassen und es gibt einen besseren Weg, Dinge zu tun?

Danke.

+0

können Sie nicht SFINAE Magie verwenden, um festzustellen, ob 'Imp :: Param' sich von' Base :: Param' unterscheidet und dass 'Imp :: myFunc()' 'Imp :: Param' als Argument verwendet? – Walter

+0

@Walter für die zweite, wenn es Vererbung gibt, wird es falsch positive haben. Für den ersten möchten Sie vielleicht nicht verlangen. – Yakk

+1

Neben: Warum sind Sie statische Umwandlung in 'T const *' von einer nicht-'const' Methode? Entweder sollte "myFunc" in "Base" und "CRTP" "const" sein, oder Sie sollten 'static_cast ' in der 'CRTP'-Implementierung aufrufen. – Yakk

Antwort

5

Warum nicht einfach einen anderen Namen für die Funktion verwenden? Dann haben Sie einen Kompilierungsfehler für jede Ableitung von CRTP Klasse ohne und Implementierung. Bedenken Sie:

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

Compilation schlägt fehl, da es keine myFuncImp von Imp vorgesehen ist.

+2

Fügen Sie "final" zu 'myFunc' in' CRTP' hinzu, um Verwechslungen zu vermeiden. – Yakk

+1

@Walter Ich habe vielleicht missverstanden, aber wenn 'myFunc' das spezialisierte myFuncImp aufruft, wie unterscheidet sich das? Ein Aufruf von 'Base :: myFunc' wird immer noch auf den richtigen 'CRTP <> :: myFuncImp'? –

+0

@Yakk vor Ort, lass mich das bearbeiten. –

0

Die Idee der Verwendung eines anderen Namens für das Mitglied der abgeleiteten Klassen (wie in Rudolfs Bundulis Antwort) ist gut. Allerdings würde ich dies eine protected Methode machen, so dass Benutzer nicht versucht sind, es zu versuchen.

Auch mit Derived::Params in der CRTP Basis wirft zusätzliche Komplikationen (seit Derived=Imp nicht vollständig an einem Punkt seiner Verwendung in CRTP<Imp> erklärt), so halten best Base::Params als Funktionsparameter im gesamten Gebäude.

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

"die unhandliche dynamic_cast <>" wird eher benötigt, da es keine Möglichkeit gibt zu wissen, ob das übergebene p vom richtigen Typ ist. Der Sinn von CRTP besteht darin, das dynamische Casting in den (multiplen) Imp-Klassen zu vermeiden. – user2746401

+0

Oh, und die Funktion geschützt (für sich) funktioniert nicht als CRTP hat keinen Zugriff auf private/geschützte Mitglieder in Imp. Allerdings fügte ich CRTP als Freund zu Imp hinzu, um das zu umgehen. – user2746401

1

Sie brechen können dynamische Polymorphie und wechseln Sie zu statischen Polymorphismus:

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

jedoch meiner Meinung nach: Die Basisklasse-Design ist ein Fehler und der Code oben ist nur eine Arbeit um.

+0

Können Sie das "Versagen" des Basisklassen-Designs näher erläutern? Wie könnte es verbessert werden? – user2746401

+0

@ user2746401 Ich kann mir keinen Anwendungsfall vorstellen. Das nächste ist die Ereignisbehandlung, aber das sollte nicht zu statischem Polymorphismus wechseln. –

Verwandte Themen