Erben von Template-Parameter
-
Hallo zusammen,
in einem anderen Thread habe ich folgenden Beispielcode gesehen:
template<T> class C : private T { //... };Mir ist klar, was es bewirkt, allerdings fehlt mir irgendwie ein Anwendungsbeispiel,
das mir dieses (potentiell) interessante Konstrukt näher bringt.
In welchem Fall muss man denn von einem Templateparameter erben?Gruß,
CSpille
-
Das nennt sich CRTP und eignet sich für Mixins oder für statische Polymorphie.
-
audacia schrieb:
Das nennt sich CRTP und eignet sich für Mixins oder für statische Polymorphie.
Nein das nennt sich nicht CRTP, das nennt sich "ganz normales Klassen-Template mit einem Parameter welcher als (private) Basisklasse verwendet wird".

DAS DA nennt sich CRTP:
template <class T> class Base { public: void Foo() { static_cast<T*>(this)->Bar(); } }; class Derived : public Base<Derived> { public: void Bar(); };@CSpille:
Da hier die Basisklasse "T" privat ist, kann man davon ausgehen, dass sie Funktionen implementiert, auf die das Template dann zugreift, um irgendwas zu machen.Vermutlich könnte das Klassen-Template "T" genauso als Member anlegen, statt privat zu erben.
Ein Beispiel wo "T" wirklich als (private!) Basisklasse nötig wäre, fällt mir grad nicht ein.
-
hustbaer schrieb:
Ein Beispiel wo "T" wirklich als (private!) Basisklasse nötig wäre, fällt mir grad nicht ein.
Stichwort Policy Based Design:
Die Klasse erbt von ihrer Basisklasse ein bestimmtes Verhalten, das sie in der Implementierung ihrer Methoden benutzt. Meist kann das auch durch Kapselung eines Objekts vom Typ T erreicht werden, allerdings kann bei Vererbung die Empty Base Optimization ausgenutzt werden, wenn die Policy-Klasse wirklich nur Verhalten und keine Daten beinhaltet. Beispiel:template <class EggCrackerPolicy> class PanCakeBaker : private EggCrackerPolicy { public: Pancake bakeTheCake() { crackTheEggs(); /* ... */ }; }; struct GentlyEggCracker { void crackTheEggs() { smoothTickTick(); } }; struct BrutalEggCracker { void crackTheEggs() { throwThemAtTheWall(); } }; int main() { PanCakeBaker<BrutalEggCracker> baker1; Pancake firstcake = baker1.bakeTheCake(); //might have pieces of eggshell in it PanCakeBaker<GentlyEggCracker> baker2; Pancake secondcake = baker2.bakeTheCake(); //much better }*SCNR*

-
@pumuckl:
OK.
Das ist dann eine Optimierung, aber keine Notwendigkeit zu erben.Fällt dir auch ein Beispiel ein wo es notwendig wäre zu erben, also wo ein "T" Member einfach nicht gehen würde?
p.S.: wenn T leer ist, dann könnte man doch einfach alle Funktionen static machen, anstatt sich auf die "empty base class optimidings" zu verlassen, nicht? Das wäre doch viel einfacher, und vor allem viel klarer, da dann keiner stundenlang grübelt, wieso Vererbung verwendet wird...
-
pumuckl schrieb:
template <class EggCrackerPolicy> class PanCakeBaker : private EggCrackerPolicy { public: Pancake bakeTheCake() { crackTheEggs(); /* ... */ }; };Kleine Korrektur: Es müsste
this->crackTheEggs();heißen, damit der Compiler auch weiß, dass das eine Elementfunktion sein soll.
Zum Thema: Mir fällt auf Anhieb auch kein Beispiel ein, wo es mehr als eine Optimierung wäre -- Optimierung im Sinne von Speicherverbrauch; denn, wenn
Tleer ist, sollte bei einem guten Compiler die KlasseC<T>, welche vonTerbt, nicht unnötig größer werden. Diese "empty base class optimization" ist aber optional. Ein Compiler darf auch Platz verschwenden. Wenn T stattdessen als Element inC<T>aufgenommen wird, muss für dieses Element mindestens ein char "verbraten" werden weil es nicht die gleiche Addresse wie ein anderes Objekt besitzen darf.Eine Anwendung, wo es als Optimierung Sinn macht: "Deleter" für einen schlauen Zeiger. Beispiel:
template<typename T> struct default_deleter { void del(T* ptr) {delete ptr;} }; template<typename T> struct default_deleter<T[]> { void del(T* ptr) {delete[] ptr;} }; template<typename T, std::size_t N> struct default_deleter<T[N]> { void del(T* ptr) {delete[] ptr;} }; template<typename T, class Deleter = default_deleter<T> > class unique_ptr : private Deleter { T* ptr; .... ~unique_ptr() {this->del(ptr);} .... T& operator*() const {return *ptr;} T* operator->() const {return ptr;} };Gruß,
SP
-
Jopp, ist mir grad selbst auch eingefallen. Es gibt ja Fälle, wo leere Policy-Klassen vorkommen können (und u.U. sogar sehr oft vorkommen), diese aber nicht leer sein müssen.
Dann macht es natürlich Sinn privat abzuleiten.
-
Meine Hauptquelle zum Thema Policy Based Design ist bisher Alexandrescus Modern C++ Design - muss da bei Gelegenheit mal nachschauen was er als Gründe für die Ableitung an Stelle der Kapselung angibt.
In den C++ Coding Standards geben er und Sutter folgende mögliche Gründe für nichtöffentliche Vererbung von T an:
- Wenn man virtuelle Funktionen überschreiben muss
- Wenn man Zugriff auf protected Member braucht
- Wenn das benutzte Objekt vor einer Basisklasse konstruiert oder nach ihr zerstört werden muss
- Im Zusammenhang mit virtuellen Basisklassen
- Wenn man weiß dass die EBO greift
- Kontrollierter Polymorphismus - für friends ist die Klasse ein T
Was die Unterstützung der EBO angeht: hier wird angegeben, welche häufig benutzten Compiler die EBO unterstützen - mittlerweile die meisten.
-
Der Artikel ist von 1997. Was soll uns das jetzt sagen?
-
Braunstein schrieb:
Der Artikel ist von 1997. Was soll uns das jetzt sagen?
Es sollte mir sagen, was eine EBO ist. Aber ich hab's nicht gefunden.
-
volkard schrieb:
Braunstein schrieb:
Der Artikel ist von 1997. Was soll uns das jetzt sagen?
Es sollte mir sagen, was eine EBO ist. Aber ich hab's nicht gefunden.
Nein. Es sollte nur sagen dass bereits 1997, also vor dem Standard bereits die meisten Compiler die EBO unterstützten. (siehe letzter Abschnitt und Nachträge im Artikel)
Was eine EBO ist kann Google schnell sagen, unter den ersten Links ist z.B. folgendes:
http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=319
http://www.gotw.ca/publications/mill07.htm
http://books.google.de/books?id=EotSAwuBkJoC&pg=PA289&lpg=PA289&dq=Empty+Base+Class+Optimization&source=bl&ots=iwK0K6-hae&sig=GYU-qNKS8Mdx8XP1TEJlnc3Pt08&hl=de&ei=eYhASv2JH4iqsAa3hvXACA&sa=X&oi=book_result&ct=result&resnum=9Auch da sind wieder einige uralt-Artikel dabei, was umso mehr zeigt dass man sich heute kaum noch Gedanken darüber zu machen braucht, ob ein verwendeter Compiler das auch wirklich kann.
Gedacht war das Ganze als Antwort auf folgenden Satz (sry dass ich das nicht mit zitiert habe):
hustbaer schrieb:
p.S.: wenn T leer ist, dann könnte man doch einfach alle Funktionen static machen, anstatt sich auf die "empty base class optimidings" zu verlassen, nicht? Das wäre doch viel einfacher, und vor allem viel klarer, da dann keiner stundenlang grübelt, wieso Vererbung verwendet wird...
Mein Fazit: Man kann sich getrost darauf verlassen dass die EBO greifen wird (und in den wenigen Fällen wo sie nicht greift schadet das Erben nicht), und man muss nicht stundenlang grübeln wieso Vererbung verwendet wird weil gerade bei der Ableitung von Templates von einem Templateparameter sehr häufig die EBO dahintersteckt, bzw. weil es einfac gängige Praxis ist.
-
hustbaer schrieb:
audacia schrieb:
<übereilter Unfug>
Nein das nennt sich nicht CRTP, das nennt sich "ganz normales Klassen-Template mit einem Parameter welcher als (private) Basisklasse verwendet wird".

DAS DA nennt sich CRTP: [...]
Lektion: beantworte keine Posts, die du mangels Zeit nur überflogen hast, besonders nicht mit nur einem Satz
