2009-04-07 10 views
8

die folgenden zwei Anwendungsszenarien In Anbetracht (genau so, wie Sie sie sehen, das heißt, wird der Endanwender nur daran interessiert sein, sich mit der Vector2_t und Vector3_t):Vererbung vs Spezialisierung

[1] Vererbung:

template<typename T, size_t N> struct VectorBase 
{ 
}; 

template<typename T> struct Vector2 : VectorBase<T, 2> 
{ 
}; 

template<typename T> struct Vector3 : VectorBase<T, 3> 
{ 
}; 

typedef Vector2<float> Vector2_t; 
typedef Vector3<float> Vector3_t; 

[2] Spezialisierung:

template<typename T, size_t N> struct Vector 
{ 
}; 

template<typename T> struct Vector<T, 2> 
{ 
}; 

template<typename T> struct Vector<T, 3> 
{ 
}; 

typedef Vector<float, 2> Vector2_t; 
typedef Vector<float, 3> Vector3_t; 

ich kann mich nicht entschließen, zu der eine schönere Lösung ist. Der offensichtliche Vorteil der Vererbung ist die Wiederverwendung von Code in den abgeleiteten Klassen. ein möglicher Nachteil ist die Leistung (größere Größe, Benutzer können nach Wert übergeben, usw.). Spezialisierung scheint all das zu vermeiden, aber auf Kosten von mir, mich mehrfach zu wiederholen.

Welche anderen Vorteile/Nachteile habe ich vermisst, und Ihrer Meinung nach, welche Route sollte ich nehmen?

Antwort

12

Was Sie schließlich wollen, glaube ich, ist der Benutzertyp

Vector<T, N> 

Und je nach N zu haben, wird der Benutzer geringfügige unterschiedliche Dinge. Der erste wird das nicht erfüllen, aber der zweite wird sich auf den Preis der Code-Duplizierung beziehen.

Was Sie tun können, ist das Erbe invertieren:

template<typename T, size_t N> struct VectorBase 
{ 
}; 

template<typename T> struct VectorBase<T, 2> 
{ 
}; 

template<typename T> struct VectorBase<T, 3> 
{ 
}; 

template<typename T, size_t N> struct Vector : VectorBase<T, N> 
{ 
}; 

Und die wenige Funktionen implementieren, die nur davon abhängen, N einiger spezifischer Wert in der entsprechenden Basisklasse zu sein. Sie können einen geschützten destructor in sie hinzufügen, löschen Benutzer Instanzen von Vector durch Zeiger auf VectorBase zu verhindern (normalerweise sollten sie nicht einmal in der Lage sein VectorBase zu nennen: diese Basen in irgendeiner Implementierung Namespace Stoßen, wie detail).

Eine weitere Idee ist es, diese Lösung mit dem in einer anderen Antwort erwähnt zu kombinieren. Vererben Sie privat (statt wie oben öffentlich) und fügen Sie der abgeleiteten Klasse Wrapperfunktionen hinzu, die die Implementierungen der Basisklasse aufrufen.

Noch eine andere Idee nur eine Klasse zu verwenden und dann enable_if (mit boost::enable_if), um sie für bestimmte Werte von N aktivieren oder zu deaktivieren, oder einen Transformator int-to-Typ verwenden, wie dies die

viel simplier ist
struct anyi { }; 
template<size_t N> struct i2t : anyi { }; 

template<typename T, size_t N> struct Vector 
{ 
    // forward to the "real" function 
    void some_special_function() { some_special_function(i2t<N>()); } 

private: 
    // case for N == 2 
    void some_special_function(i2t<2>) { 
     ... 
    } 

    // case for N == 3 
    void some_special_function(i2t<3>) { 
     ... 
    } 

    // general case 
    void some_special_function(anyi) { 
     ... 
    } 
}; 

auf diese Weise ist es völlig transparent für den Benutzer von Vector. Es fügt auch keinen Platzbedarf für Compiler hinzu, die die leere Basisklassenoptimierung durchführen (ziemlich häufig).

+0

Die vollständigste Antwort. Vielen Dank! –

+0

Nett und COOL Antwort. Nimmt einen Guru, um eine so elegante Antwort zu bekommen. –

+0

+1. Ich vermute, Ihr Vorschlag von enable_if <> in einer einzelnen Klassenvorlage ist der beste Weg - sicherlich würde der einzige Unterschied zwischen den Vorlagenklassen für verschiedene Vektordimensionen die Anzahl der Parameter im Konstruktor sein? –

0

Wenn Sie die Vorlagenspezialisierung zu sehr verwenden, müssen Sie wahrscheinlich Ihr Design überdenken. Bedenkt, dass Sie es hinter einer typedef verstecken, bezweifle ich, dass Sie es brauchen.

4

Vererbung und private Vererbung verwenden. Und verwende keine virtuellen Funktionen. Da Sie bei privater Vererbung kein is-a haben, kann niemand einen baseclas-Zeiger auf eine abgeleitete Unterklasse verwenden, und Sie erhalten das Problem beim Schneiden nicht, wenn passinfg nach Wert übergeben wird.

Dies gibt Ihnen das Beste aus beiden Welten (und in der Tat implementieren die meisten Bibliotheken viele der STL-Klassen).

Von http://www.hackcraft.net/cpp/templateInheritance/ (diskutieren std :: vector, nicht Ihre V ector Klasse):

vector<T*> eine private Basis von vector<void*> haben erklärt. Alle Funktionen, die in den Vektor ein neues Element zu platzieren, wie push_back(), rufen die äquivalente Funktion auf dieser privaten Basis, so intern unsere vector<T*> ein vector<void*> für die Lagerung verwendet wird. Alle Funktionen , die ein Element aus dem Vektor zurückgeben, wie front(), führen eine static_cast auf dem Ergebnis des Aufrufs der entsprechenden Funktion auf der privaten Basis. Da der einzige Weg, um einen Zeiger in die vector<void*> (abgesehen von absichtlich gefährliche Tricks) zu bekommen, ist durch die Schnittstelle von vector<T*> angeboten, es ist sicher die void* zu T* (oder die void*& zurück zu T*& zurück geworfen staticly und bald).

Im Allgemeinen, wenn die STL es so macht, scheint es wie ein anständiges Modell zu emulieren.

+0

Das ist ein guter Punkt, ich habe die STL total vergessen. –

+0

Ein Nitpick: Streng genommen haben Sie "ist ein", aber das "ist ein" ist nicht öffentlich. Z.B. a 'std: vector ' ist ein 'std: vector ' als ein Mittel, um Dinge mit guter Raumeffizienz zu implementieren. Aber das „ein“ ist privat, so weit wie jeder Code außerhalb betrifft, 'std: Vectory ' ist kein 'std: Vectory ', weil sie nicht „sehen“ die Beziehung. So streng Sie haben, ist-a, aber in der Praxis Sie nicht :) –

0

Vererbung sollte nur modelliert werden „ist-a“. Spezialisierung wäre die sauberere Alternative. Wenn Sie die Vererbung aus irgendeinem Grund benötigen oder verwenden möchten, müssen Sie zumindest eine private oder geschützte Vererbung vornehmen, damit Sie nicht öffentlich von einer Klasse mit einem öffentlichen nicht virtuellen Destruktor erben.

Ja, die Metaprogrammierung Jungs tun immer

struct something : something_else {}; 

Aber diese something s sind Metafunktionen und nicht dazu gedacht, als Typen verwendet werden.