2010-05-12 11 views
19

Das gesamte Material, das ich auf Curiously Recurring Template Pattern gelesen habe, scheint auf einer Vererbungsschicht zu liegen, dh Base und Derived : Base<Derived>. Was, wenn ich noch einen Schritt weiter gehen möchte?Wie schreibe ich merkwürdig wiederkehrende Vorlagen mit mehr als 2 Vererbungsebenen?

#include <iostream> 
using std::cout; 


template<typename LowestDerivedClass> class A { 
public: 
    LowestDerivedClass& get() { return *static_cast<LowestDerivedClass*>(this); } 
    void print() { cout << "A\n"; } 
}; 
template<typename LowestDerivedClass> class B : public A<LowestDerivedClass> { 
    public: void print() { cout << "B\n"; } 
}; 
class C : public B<C> { 
    public: void print() { cout << "C\n"; } 
}; 

int main() 
{ 
    C c; 
    c.get().print(); 

// B b;    // Intentionally bad syntax, 
// b.get().print(); // to demonstrate what I'm trying to accomplish 

    return 0; 
} 

Wie kann ich diesen Code neu schreiben, ohne Fehler und Anzeige

C
B

Mit c.get(). Print() und b.get zu kompilieren ().drucken() ?

Motivation: ich drei Klassen Angenommen haben,

class GuiElement { /* ... */ }; 
class Window : public GuiElement { /* ... */ }; 
class AlertBox : public Window { /* ... */ }; 

Jede Klasse dauert 6 oder so Parameter in ihrem Konstruktor, von denen viele sind optional und haben vernünftige Standardwerte. Um avoid the tedium of optional parameters, das beste solution ist die Named Parameter Idiom zu verwenden.

Ein grundlegendes Problem mit diesem Idiom ist, dass die Funktionen der Parameterklasse das Objekt zurückgeben müssen, an dem sie aufgerufen werden. Einige Parameter werden jedoch an GuiElement, einige an Window und einige an AlertBox übergeben. Sie müssen einen Weg, dies zu schreiben:

AlertBox box = AlertBoxOptions() 
    .GuiElementParameter(1) 
    .WindowParameter(2) 
    .AlertBoxParameter(3) 
    .create(); 

Doch dies wahrscheinlich fehlschlagen würde, weil zum Beispiel GuiElementParameter (int) wahrscheinlich wieder GuiElementOptions &, die nicht über eine WindowParameter (int) -Funktion.

Dies war zuvor asked, und die Lösung scheint etwas Geschmack der seltsam wiederkehrenden Vorlage Muster zu sein. Der besondere Geschmack, den ich verwende, ist here.

Es ist eine Menge Code zu schreiben, jedes Mal wenn ich ein neues Gui Element erstelle. Ich habe nach Wegen gesucht, es zu vereinfachen. Ein Hauptgrund für die Komplexität ist die Tatsache, dass ich CRTP verwende, um das Named-Parameter-Idiom-Problem zu lösen, aber ich habe drei Ebenen, nicht zwei (GuiElement, Window und AlertBox) und meine current workaround vervierfacht die Anzahl der Klassen, die ich habe. (!) Zum Beispiel Window, WindowOptions, WindowBuilderT und WindowBuilder.

Das bringt mich zu meiner Frage, wobei ich im Wesentlichen nach einer eleganteren Methode suche, CRTP auf langen Vererbungsketten wie GuiElement, Window und Alertbox zu verwenden.

+0

Möchten Sie 'c.get(). Print()' ausgeben "C \ nB \ n" oder möchten Sie die Zeilen, die Sie auskommentiert kompilieren und die "B \ n "halb? –

+0

Letzteres. Bearbeitete meine Frage, um hoffentlich klarer zu sein. – Kyle

+0

Sie sollten angeben, wofür Sie dies wünschen. Da es die einzige mögliche Antwort ist: nein, du kannst nicht. –

Antwort

2

Hier ist, worauf ich mich festgelegt habe, eine Variation von CRTPs zu verwenden, um das Problem zu lösen, das in meinem Motivationsbeispiel dargestellt wird. Wahrscheinlich las am besten von unten nach oben und Scrollen nach oben ..

#include "boost/smart_ptr.hpp" 
using namespace boost; 

// *** First, the groundwork.... 
//  throw this code in a deep, dark place and never look at it again 
// 
//  (scroll down for usage example) 

#define DefineBuilder(TYPE, BASE_TYPE) \ 
    template<typename TargetType, typename ReturnType> \ 
    class TemplatedBuilder<TYPE, TargetType, ReturnType> : public TemplatedBuilder<BASE_TYPE, TargetType, ReturnType> \ 
    { \ 
    protected: \ 
     TemplatedBuilder() {} \ 
    public: \ 
     Returns<ReturnType>::me; \ 
     Builds<TargetType>::options; \ 

template<typename TargetType> 
class Builds 
{ 
public: 
    shared_ptr<TargetType> create() { 
     shared_ptr<TargetType> target(new TargetType(options)); 
     return target; 
    } 

protected: 
    Builds() {} 
    typename TargetType::Options options; 
}; 

template<typename ReturnType> 
class Returns 
{ 
protected: 
    Returns() {} 
    ReturnType& me() { return *static_cast<ReturnType*>(this); } 
}; 

template<typename Tag, typename TargetType, typename ReturnType> class TemplatedBuilder; 
template<typename TargetType> class Builder : public TemplatedBuilder<TargetType, TargetType, Builder<TargetType> > {}; 

struct InheritsNothing {}; 
template<typename TargetType, typename ReturnType> 
class TemplatedBuilder<InheritsNothing, TargetType, ReturnType> : public Builds<TargetType>, public Returns<ReturnType> 
{ 
protected: 
    TemplatedBuilder() {} 
}; 

// *** preparation for multiple layer CRTP example *** // 
//  (keep scrolling...) 

class A    
{ 
public: 
    struct Options { int a1; char a2; }; 

protected: 
    A(Options& o) : a1(o.a1), a2(o.a2) {} 
    friend class Builds<A>; 

    int a1; char a2; 
}; 

class B : public A 
{ 
public: 
    struct Options : public A::Options { int b1; char b2; }; 

protected: 
    B(Options& o) : A(o), b1(o.b1), b2(o.b2) {} 
    friend class Builds<B>; 

    int b1; char b2; 
}; 

class C : public B 
{ 

public: 
    struct Options : public B::Options { int c1; char c2; }; 

private: 
    C(Options& o) : B(o), c1(o.c1), c2(o.c2) {} 
    friend class Builds<C>; 

    int c1; char c2; 
}; 


// *** many layer CRTP example *** // 

DefineBuilder(A, InheritsNothing) 
    ReturnType& a1(int i) { options.a1 = i; return me(); } 
    ReturnType& a2(char c) { options.a2 = c; return me(); } 
}; 

DefineBuilder(B, A) 
    ReturnType& b1(int i) { options.b1 = i; return me(); } 
    ReturnType& b2(char c) { options.b2 = c; return me(); } 
}; 

DefineBuilder(C, B) 
    ReturnType& c1(int i) { options.c1 = i; return me(); } 
    ReturnType& c2(char c) { options.c2 = c; return me(); } 
}; 

// note that I could go on forever like this, 
// i.e. with DefineBuilder(D, C), and so on. 
// 
// ReturnType will always be the first parameter passed to DefineBuilder. 
// ie, in 'DefineBuilder(C, B)', ReturnType will be C. 

// *** and finally, using many layer CRTP builders to construct objects ***/ 

int main() 
{ 
    shared_ptr<A> a = Builder<A>().a1(1).a2('x').create(); 
    shared_ptr<B> b = Builder<B>().a1(1).b1(2).a2('x').b2('y').create(); 
    shared_ptr<B> c = Builder<C>().c2('z').a1(1).b1(2).a2('x').c1(3).b2('y').create(); 
    // (note: any order works) 

    return 0; 
}; 
7

Ich bin nicht ganz klar, was Sie hoffen, zu erreichen, aber das ist eine enge Annäherung von dem, was Sie scheinen zu fragen.

template<typename LowestDerivedClass> class A { 
public: 
    LowestDerivedClass& get() { return *static_cast<LowestDerivedClass*>(this); } 
    void print() { cout << "A"; } 
}; 
template<typename LowestDerivedClass> class Bbase 
    : public A<LowestDerivedClass> { 
public: void print() { cout << "B"; this->A<LowestDerivedClass>::print(); } 
}; 

class B : public Bbase<B> { 
}; 

class C : public Bbase<C> { 
public: void print() { cout << "C"; this->Bbase<C>::print(); } 
}; 

int main() { 
    C c; 
    c.print(); 
    cout << endl; 
    B b; 
    b.print(); 
    cout << endl; 
} 

Ich änderte die Ausgabe, um die Vererbung besser zu veranschaulichen. In deinem ursprünglichen Code kannst du nicht so tun, als ob B keine Vorlage wäre [das Beste, auf das du hoffen kannst, ist B<>], also ist so etwas wahrscheinlich die klügste Art, es zu handhaben.


Von Ihrer anderen Antwort ist (2) nicht möglich. Sie können Template-Parameter für Funktionen auslassen, wenn die Argumente der Funktion ausreichen, um auf sie zu schließen, aber bei Klassen müssen Sie etwas bereitstellen. (1) kann getan werden, aber es ist peinlich. Leaving all die verschiedenen Schichten:

template<typename T> struct DefaultTag { typedef T type; }; 
template<typename Derived = void> 
class B : public A<Derived> { /* what B should do when inherited from */ }; 
template<> 
class B<void> : public A<DefaultTag<B<void> > > { /* what B should do otherwise */ }; 

Sie müssen etwas ähnliches auf jeder Ebene tun. Wie gesagt, ungeschickt. Sie können nicht einfach typename Derived = DefaultTag<B> > oder etwas ähnliches sagen, weil B noch nicht existiert.

+0

"Ich bin nicht ganz klar auf, was Sie zu erreichen hoffen" - fügte eine längere Motivation Abschnitt zu meiner Frage hinzu. – Kyle

+0

Ich kam mit einem sehr ähnlichen Vorschlag, aber ich hatte auch eine 'Cbase'-Klasse (das gleiche Muster wie' Abase' und 'Bbase';' C' daraus abgeleitet anstelle von 'Bbase'. Der einzige Vorteil, AFAIK, ist Dadurch können Sie das Muster erweitern, ohne die vorhandene Vererbung zu ändern. –

0

Ich denke, es ist unmöglich, einige generische Mechanismen zu implementieren. Sie müssen den genauen Template-Parameter jedes Mal explizit angeben, wenn Sie die Basisklasse erben, unabhängig davon, wie viele Ebenen der Indirektion dazwischen liegen (nach Ihrer Antwort: jetzt gibt es 2 Ebenen: Sie übergeben C nicht direkt an die Basis, sondern C in eine Tag-Struktur eingewickelt, sieht es aus wie eine Schlange, die ihren eigenen Schwanz beißt)

Wahrscheinlich wäre es für Ihre Aufgabe besser, Typ Löschung zu verwenden, und nicht seltsam wiederkehrende Vorlage Muster. Kann sein, this wird nützlich sein