Ableitung durch Policy ersetzen
-
Habe das mal eingebaut im Beispielcode.
Aber irgendwie macht die Kombination von Policies und CRTP keinen Sinn, da sich dann eine Kreisvererbung ergibt.
Hier der Code:
#include <string> #include <vector> template <typename FooPolicy, typename T, typename U = T> class Base : public FooPolicy{ public: using FooPolicy::doFoo; Base (int whatever): m_whatever(whatever) {} ~Base() { /* machs kaputt */ } void foo(const T& t) { doFoo(t); } protected: int m_whatever; // weitere Membervariablen, die von U und T abhängen, weggelassen }; template <typename CRTPBase, typename T, typename U = T> class APolicy : public CRTPBase { public: APolicy(const std::string& bar): m_bar(bar) {} void doFoo(const T& t) { doSomething(t, this->whatever, m_bar); } private: std::string m_bar; }; template <typename CRTPBase, typename T, typename U = T> class BPolicy: public CRTPBase { public: BPolicy(float baz): m_baz(baz) {} void doFoo(const T& t) { doSomethingElse(t, this->whatever, m_baz); } private: float m_baz; }; // irgendwo anders Base<APolicy<typename Base, int>, int> stuff(12);gcc 4.4 versteht ihn nicht:
../crtp-test/main.cpp:49:32: error: wrong number of template arguments (1, should be 3) ../crtp-test/main.cpp:23:7: error: provided for ‘template<class CRTPBase, class T, class U> class APolicy’ ../crtp-test/main.cpp:49:38: error: template argument 1 is invalid ../crtp-test/main.cpp:49:45: error: invalid type in declaration before ‘(’ tokenund ich ehrlich gesagt gerade auch nicht.
Base<APolicy<Base> > ist doch eigentlich Base<APolicy<Base<APolicy< ....
Da habe ich doch eine seltsame Schleife konstruiert, oder???Philipp
-
Du sollst es ja auch nicht mehr über Policies machen, sondern wieder Deinen ersten Ansatz nehmen. Nur eben mit statischer anstatt mit dynamischer Polymorphie. Sonst wird es schwierig, mit den unterschiedlichen Konstruktoren...
-
Achtung:
class Base: public Derived<Base>ist natürlich verkehrtherum.
es mussclass Derived : public Base<Derived>heissen.
Dann hast du statt
template <typename CRTPBase, typename T, typename U = T> class APolicy : public CRTPBase {einfach ein
template <typename T, typename U = T> class APolicy : public Base<APolicy, T, U> {APolicy weiss ja wer seine Base ist.
Und Base erbt natürlich nicht von APolicy.
Sondern sieht nur so aus:template <typename FooPolicy, typename T, typename U = T> class Base{Base weiss aber dass es in Wirklichkeit ein FooPolicy ist und kann daher folgendes machen:
void foo() { static_cast<FooPolicy*>(this)->doFoo(); }um eine virtuelle doFoo funktion zu simulieren.
PS:
da Base ja über FooPolicy zugriff auf T und U bekommen kann braucht es selber diese garnicht als template parameter...
-
Ha!
So geht's:template <typename Policy, typename T, typename U = T> class Base { public: Base (int whatever): m_whatever(whatever) {} ~Base() { /* machs kaputt */ } void foo(const T& t) { static_cast<Policy*>(this)->doFoo(t); } protected: int m_whatever; // weitere Membervariablen, die von U und T abhängen, weggelassen }; template <typename T, typename U = T> class APolicy : public Base<APolicy<T,U>, T, U> { public: APolicy(const std::string& bar, int whatever): Base<APolicy, T,U>(whatever), m_bar(bar) {} void doFoo(const T& t) { doSomething(t, this->whatever, m_bar); } private: std::string m_bar; }; template <typename T, typename U = T> class BPolicy: public Base<BPolicy<T,U>, T, U> { public: BPolicy(float baz, int whatever): Base<BPolicy, T, U>(whatever), m_baz(baz) {} void doFoo(const T& t) { doSomethingElse(t, this->whatever, m_baz); } private: float m_baz; }; // irgendwo anders APolicy<int> stuff("Hallo", 42);Bleibt die Frage:
Wann setze ich denn dann Policies ein, wenn statische Polymorphie durch CRTP erreicht wird?Philipp
-
@ShadeOfMine: GMTA oder FSD ?

-
PhilippM schrieb:
Bleibt die Frage:
Wann setze ich denn dann Policies ein, wenn statische Polymorphie durch CRTP erreicht wird?Policies haben ja weniger mit der herkömmlichen Polymorphie zu tun, sondern mehr mit dem Strategy Design Pattern. Policies bieten dir die Möglichkeit teile der Klasse unterschiedlich zu konfigurieren.
Ein Beispiel von Policies sind zB die allocatoren in der STL. Wenn diese auch etwas grottig sind. char_traits von Strings sind ein anderes Beispiel.
Du nimmst Policies also zum verändern eines Teils des Verhaltens. Während du Vererbung einsetzt um das Verhalten zu erweitern.
Natürlich können die Anwendungszwecke verschwimmen. zB ist in .NET eine SynchronizedList von List abgeleitet, obwohl man Synchronization auch als Policy einbauen könnte. Wo Ableitung aber Probleme macht ist, wenn man 2 Teile konfigurieren will. zB Synchronization und OwnerShip der Elemente in der Liste (soll die Liste selber das delete machen, wie boost::ptr_list oder sich wie std::list verhalten).
Man müsste für Vererbung nun eine SynchronizedList und eine OwningList und einen OwningSynchronizedList erstellen. Mit Policies macht man einfach: List<Synchronized, Owning>.
Was man durch Policy Based Design aber verliert ist die Polymorphie.
PS:
Wohl eher: ich poste zu langsam oder du denkst zu schnell
-
Shade Of Mine schrieb:
Policies haben ja weniger mit der herkömmlichen Polymorphie zu tun, sondern mehr mit dem Strategy Design Pattern. Policies bieten dir die Möglichkeit teile der Klasse unterschiedlich zu konfigurieren.
Was man durch Policy Based Design aber verliert ist die Polymorphie.Danke! Das ist der Denkansatz, der mir gefehlt hat.
Jetzt hab ich aber noch eine low-level Frage: Was bringt mir das fürn Vorteil gegenüber den virtuellen Funktionen?
Bei virtellen Funktionen muss ich den Funktionspointer dereferenzieren, hier muss ich
static_cast<Policy*>(this)->doFoo(t);ebenfalls dem Pointer folgen, um an die Funktion zu kommen.
Wo ist da jetzt der Laufzeitvorteil der "Compilezeitpolymorphie"?
Philipp
-
PhilippM schrieb:
Jetzt hab ich aber noch eine low-level Frage: Was bringt mir das fürn Vorteil gegenüber den virtuellen Funktionen?
Bei virtellen Funktionen muss ich den Funktionspointer dereferenzieren, hier muss ich
static_cast<Policy*>(this)->doFoo(t);ebenfalls dem Pointer folgen, um an die Funktion zu kommen.
Wo ist da jetzt der Laufzeitvorteil der "Compilezeitpolymorphie"?
Virtuelle funktionen kosten mehr als nur eine zeiger dereferenzierung.
Der cast hier beim crtp ist aber gratis, static_cast ist das in der regel. Es wird ja nur gesagt: das ist ein foo.Bei virtuellen funktionen hast du ein dynamisches dispatching da this ja auf alles zeigen kann. Und sobald du eine virtuelle funktion hast brauchst einen rattenschwanz an runtime type identification (um imer wissen zu koennen was this ist). Weitesr kann man virtuelle funktionen nicht gut inlinen.
Klingt jetzt als waeren virtuelle funktionen sehr teuer, sind sie aber nicht. In java ist zb jede funktion virtuell und es ist kein problem. Es ist also eine minimale optimierung, mehr nicht.
-
Super, danke für die Erklärung.
Na, dann bin ich ja mal auf den nächsten Profile-Lauf gespannt. Die "foo" funktion wird nämlich auf ~500 Objekten 50 mal pro Sekunde aufgerufen. Ob sich da was bemerkbar macht?Philipp
-
PhilippM schrieb:
Ob sich da was bemerkbar macht?
Poste dann bitte die Ergebnisse.
Performance haengt an vielen Faktoren. Schneller wird es sicher sein, aber ob es merkbar schneller ist - das wird spannend.PS:
mein letzter Post war in der UBahn geschrieben, nicht dass ich sonst so eine tolle Rechtschreibung haette, aber *brr* sieht der grottig aus.
*Note to self* In der UBahn nur lesen, nicht schreiben.
-
Jetzt habe ich aber wieder ein Problem:
Vorher:
class FooInt { public: FooInt(Base<int>* base_pointer): m_base_pointer(base_pointer); private: Base<int>* m_base_pointer; };hängt ja jetzt vom konkreten Typ ab.
Daher jetzt:
template <template <typename T> class Concrete> class FooInt { public: FooInt(Concrete<int>* data); private: Concrete<int>* m_concrete_pointer; };Wenn ich den Konstruktor für int implementieren will:
template <template <> class Concrete<int> > FooInt<Concrete<int> >::FooInt(Concrete<int>* data): m_concrete_pointer(data) { }Fliegt mir alles um die Ohren:
error: expected identifier before ‘>’ token error: expected ‘>’ before ‘<’ token error: type/value mismatch at argument 1 in template parameter list for ‘template<int <anonymous> > class Concrete’ expected a constant of type ‘int’, got ‘int’ error: template argument 1 is invalid type/value mismatch at argument 1 in template parameter list for ‘template<int <anonymous> > class Concrete’ expected a constant of type ‘int’, got ‘int’ error: declaration of template ‘template<template<int <anonymous> > class Concrete> int FooInt(int*)’
Hilfe!
Muss ich den Base<int>* pointer jetzt durch einen Concrete<int>* pointer ersetzen oder durch einen Base<Concrete<int>, int>* Pointer?
-
Disregard.
So, das Refaktoring ist durch. Meine Fresse, hab ich da wieder write-only Code produziert.
Ich zitiere mal zusammenhanglos einen Konstruktor, der heute entstanden ist:
template <template <typename T, typename U=T> class SimAccess, typename Packet, typename Metadata, typename SimType, typename CalcType = SimType, typename BusType = CalcType> class SendableData : public TypelessSendableData<Packet, Metadata> { public: SendableData(SimAccess<SimType, CalcType>* sim_data, PrioType priority = Constant, SimAccess<int>* suspend_ref = 0, SimAccess<int>* dyn_length_ref = 0): m_sim_data(sim_data), m_priority(priority), m_force_outdated(false), ....Man gebe sich mal die länge der Template-Argumente.
Aber erster positiver Effekt: Das Binary ist etwa 5% kleiner geworden!Bin mal gespannt aufs Laufzeitverhalten.
Philipp