Frage

Was ist die Beziehung virtuelle Funktionen und C ++ Vererbungsmechanismen im Vergleich mit Vorlagen und so etwas wie Boost-Konzepte zwischen der Verwendung?

Es scheint, als gäbe es durchaus eine Überlappung von dem, was möglich ist. Das heißt, scheint es möglich zu sein, polymorphes Verhalten mit beiden Ansätzen zu erreichen. Also, wann ist es sinnvoll, einen über den anderen zu bevorzugen?

Der Grund, warum ich das oben bringen, weil ich einen Templat-Behälter haben, wo die Behälter selbst eine hierarchische Beziehung haben. Ich möchte Algorithmen schreiben, die diese Behälter verwenden, ohne die Sorge um welche spezifischen Behälter ist. Auch würden profitieren einige Algorithmen aus dem Wissen, dass der Template-Typ erfüllte bestimmte Konzepte (Comparable, zum Beispiel).

Also, auf der einen Seite, ich will Container polymorphicly verhalten. Auf der anderen Seite habe ich noch Konzepte verwenden, wenn ich richtig einige Algorithmen implementieren wollen. Was ist ein Junior-Entwickler zu tun?

War es hilfreich?

Lösung

Ich denke, von Begriffen als eine Art Meta-Schnittstelle. Sie kategorisieren Arten nach ihren Fähigkeiten. Die nächsten C ++ Version liefert nativen Konzepte. Ich hatte es nicht verstanden, bis ich über C ++ 1x die Konzepte kamen und wie sie unterschiedliche, aber nicht verwandten Arten zusammen erlauben setzen. Stellen Sie sich eine Range-Schnittstelle verfügen. Sie können, dass mit zweierlei Weise modellieren. Eines ist eine Subtyp Beziehung :

class Range {
    virtual Iterator * begin() = 0;
    virtual Iterator * end() = 0;

    virtual size_t size() = 0;
};

Natürlich, jede Klasse, die von diesen ableitet implementiert die Reichweite Schnittstelle und kann mit Ihren Funktionen verwendet werden. Aber jetzt sehen Sie, es ist begrenzt. Was ist ein Array? Es ist ein Bereich auch!

T t[N];

begin() => t
end() => t + size()
size() => N

Leider kann man nicht einen Array aus dieser Range-Klasse ableiten, die eine Schnittstelle zu implementieren. Sie benötigen eine zusätzliche Methode ( Überlastung ). Und was Dritte Container? Ein Benutzer Ihrer Bibliothek könnte ihre Container will mit Ihren Funktionen zusammen verwenden. Aber er kann nicht die Definition ihrer Container ändern. Hier Konzepte kommen in Spiel:

auto concept Range<typename T> {
    typename iterator;
    iterator T::begin();
    iterator T::end();
    size_t T::size();
}

Nun, sagen Sie etwas über die unterstützten Operationen eines Typs, die erfüllt werden kann, wenn T die entsprechenden Mitgliedsfunktionen. In Ihrer Bibliothek würden Sie die Funktion generic schreiben. Auf diese Weise können Sie jede Art akzeptieren solange es die erforderlichen Operationen unterstützt:

template<Range R>
void assign(R const& r) {
    ... iterate from r.begin() to r.end(). 
}

Es ist eine großartige Art von Ersetzbarkeit . Alle Typ passen die Rechnung, die dem Konzept haftet, und nicht nur jene Typen, die aktiv etwas Schnittstelle implementieren. Die nächste C ++ Standard-geht weiter: Es definiert ein Container Konzept, das durch reines Arrays passen wird (durch caled etwas Konzept Karte , die definiert, wie eine Art paßt etwas Konzept) und andere, vorhandenen Standardcontainer.

  

Der Grund, warum ich das oben bringen, weil ich einen Templat-Behälter haben, wo die Behälter selbst eine hierarchische Beziehung haben. Ich möchte Algorithmen schreiben, die diese Behälter verwenden, ohne die Sorge um welche spezifischen Behälter ist. Auch würden profitieren einige Algorithmen aus dem Wissen, dass der Template-Typ erfüllte bestimmte Konzepte (Comparable, zum Beispiel).

Sie können beide tatsächlich tun mit Vorlagen. Sie können halten Sie Ihre hierarchische Beziehung mit Code zu teilen, und dann die Algorithmen in einer generischen Art und Weise zu schreiben. Zum Beispiel, um mitzuteilen, dass Ihr Behälter vergleichbar ist. Das ist wie Standard-Random-Access / Vorwärts / Ausgang / Eingang Iteratorkategorien sind implementiert:

// tag types for the comparator cagetory
struct not_comparable { };
struct basic_comparable : not_comparable { };

template<typename T>
class MyVector : public BasicContainer<T> {
    typedef basic_comparable comparator_kind;
};

/* Container concept */
T::comparator_kind: comparator category

Es ist eine vernünftige einfache Art und Weise, es zu tun, eigentlich. Jetzt können Sie eine Funktion aufrufen, und es wird auf die korrekte Umsetzung übermitteln.

template<typename Container>
void takesAdvantage(Container const& c) {
    takesAdvantageOfCompare(c, typename Container::comparator_kind());
}

// implementation for basic_comparable containers
template<typename Container>
void takesAdvantage(Container const& c, basic_comparable) {
    ...
}

// implementation for not_comparable containers
template<typename Container>
void takesAdvantage(Container const& c, not_comparable) {
    ...
}

Es gibt tatsächlich verschiedene Techniken, die verwendet werden können, dass zu implementieren. Ein anderer Weg ist boost::enable_if zu verwenden, um verschiedene Implementierungen jedes Mal zu aktivieren oder deaktivieren.

Andere Tipps

Ja, polymorphes Verhalten ist möglich mit beiden Mechanismen. In der Tat sind beide genannt Polymorphismus zu.

Virtuelle Funktionen geben Ihnen dynamischen Polymorphismus (weil es zur Laufzeit entschieden wird), während Vorlagen geben Ihnen statischen Polymorphismus (alles wird zur Compile-Zeit entschieden).

Und das soll die Frage, welche Antwort auch bevorzugen. Wann immer möglich, bevorzugt Arbeit zu bewegen, zu kompilieren Zeit. Also, wenn Sie mit ihm weg erhalten können, verwenden Vorlagen, um Ihre Polymorphismus Bedürfnisse zu lösen. Und wenn das nicht möglich ist (weil Sie Laufzeittypinformationen verwenden müssen, weil die genauen Typen sind nicht zur Compile-Zeit bekannt) zurückgreifen, um dynamische Polymorphie.

(Natürlich kann es andere Gründe geben, den einen oder anderen zu bevorzugen. Insbesondere Vorlagen Sie benötigen eine Menge Code, um Dateien auf Header, die nicht ein Problem sein kann oder nicht, und Übersetzungsgeschwindigkeit neigt dazu, zu leiden, die auch kann oder auch nicht ein Problem sein.)

Wenn eine Entscheidung zur Compile-Zeit hergestellt werden, verwenden Sie Vorlagen. Ansonsten verwenden Sie Vererbung und virtuelle Funktionen.

In diesem speziellen Fall können Sie wie etwas tun

template<typename T>
class ContainerBase{};

template<typename T>
class ContainerDerived : public ContainerBase<T> {};

Da jeder ‚Container‘ Typ für jeden Vorlagentyp einzigartig ist, gibt es keinen Grund Mitgliederfunktionen eines jeden Containertyp nicht auf dem Templat-Typ der Züge spezialisiert werden.

Als einfaches Beispiel für den Unterschied zwischen Kompilierung-und Laufzeit-Polymorphismus des folgenden Code betrachten:

template<typename tType>
struct compileTimePolymorphism
{ };

// compile time polymorphism,
// you can describe a behavior on some object type
// through the template, but you cannot interchange 
// the templates
compileTimePolymorphism<int> l_intTemplate;
compileTimePolymorphism<float> l_floatTemplate;
compileTimePolymorphism *l_templatePointer; // ???? impossible

struct A {};
struct B : public A{};
struct C : public A{};

// runtime polymorphism 
// you can interchange objects of different type
// by treating them like the parent
B l_B;
C l_C:
A *l_A = &l_B;
l_A = &l_C;

Compile-Zeit Polymorphismus ist eine gute Lösung, wenn das Verhalten eines Objekts auf einem anderen Objekt abhängig ist. Laufzeit-Polymorphismus ist erforderlich, wenn das Verhalten eines Objekts geändert werden muss.

Die beiden können durch die Definition einer Vorlage kombiniert werden, die polymorph ist:

template<typename tType>
struct myContainer : public tType
{};

Die Frage ist dann, wenn das Verhalten des Containers (Runtime-Polymorphismus) geändert werden muss, und wo das Verhalten auf den Objekten hängt es (Kompilierung Polymorphismus).

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top