Grundlagen der C ++ - Vorlage: Funktionsvorlagen

Haftungsausschluss: Der Artikel wurde bereits im Februar gestartet, aber aus von mir abhängigen Gründen nicht fertiggestellt. Das Thema ist sehr umfangreich und wird daher in abgeschnittener Form veröffentlicht. Was nicht passte, wird später berücksichtigt.



Es ist unmöglich, modernes C ++ zu verstehen, ohne zu wissen, was Programmiermuster sind. Diese Eigenschaft der Sprache eröffnet vielfältige Möglichkeiten zur Optimierung und Wiederverwendung von Code. In diesem Artikel werden wir versuchen herauszufinden, was es ist und wie alles funktioniert.

Der Mechanismus von Vorlagen in C ++ ermöglicht es uns, das Problem der Vereinheitlichung des Algorithmus für verschiedene Typen zu lösen: Es müssen keine unterschiedlichen Funktionen für Ganzzahl-, Real- oder Benutzertypen geschrieben werden. Es reicht aus, einen verallgemeinerten Algorithmus zu kompilieren, der nicht vom Datentyp abhängig ist und nur auf gemeinsamen Eigenschaften basiert. Beispielsweise kann der Sortieralgorithmus sowohl mit Ganzzahlen als auch mit Objekten vom Typ "Auto" arbeiten.

Es gibt Funktionsvorlagen und Klassenvorlagen.

Funktionsvorlagen sind eine allgemeine Beschreibung des Verhaltens von Funktionen, die für Objekte unterschiedlichen Typs aufgerufen werden können. Mit anderen Worten, eine Funktionsvorlage (Vorlagenfunktion, verallgemeinerte Funktion) ist eine Familie verschiedener Funktionen (oder eine Algorithmusbeschreibung). In der Beschreibung ähnelt die Funktionsvorlage einer regulären Funktion: Der Unterschied besteht darin, dass einige Elemente nicht definiert sind (Typen, Konstanten) und parametrisiert sind.

Klassenvorlagen - eine allgemeine Beschreibung eines Benutzertyps, in dem Attribute und Typoperationen parametrisiert werden können. Sie sind Konstruktionen, mit denen reale Klassen generiert werden können, indem konkrete Argumente anstelle von Parametern eingesetzt werden.

Betrachten wir Funktionsvorlagen genauer.

Funktionsvorlagen


Wie schreibe ich die erste Vorlagenfunktion?


Betrachten Sie den Fall der Bestimmung des minimalen Elements von zwei. Bei ganzen Zahlen und reellen Zahlen müssen Sie 2 Funktionen schreiben.

int _min(int a, int b){ if( a < b){ return a; } return b; } double _min(double a, double b){ if( a < b){ return a; } return b; } 

Sie können natürlich nur eine Funktion mit gültigen Parametern implementieren, aber zum Verständnis der Muster ist dies schädlich.
Was passiert, wenn die Anwendung kompiliert wird? Beide Implementierungen der Funktion fallen in den Binärcode der Anwendung, auch wenn sie nicht verwendet werden (da die Compiler jetzt jedoch sehr intelligent sind, können sie nicht verwendeten Code ausschneiden). Und wenn Sie eine Funktion hinzufügen müssen, die das Minimum von 2 Zeilen definiert (es ist schwer vorstellbar, ohne anzugeben, dass es eine minimale Zeile gibt) ?!

In diesem Fall können Sie eine Funktionsvorlage definieren, wenn der Algorithmus für die Typen, mit denen Sie arbeiten müssen, gleich ist. Das Prinzip wird im allgemeinen Fall wie folgt sein:

  1. Für einen Typ wird eine Funktionsimplementierung durchgeführt.
  2. Die Header-Vorlage <Klassentyp> (oder Vorlage <Typname-Typ>) wird zugewiesen. Dies bedeutet, dass der Algorithmus eine Art abstrakten Typtyp verwendet.
  3. Bei der Implementierung der Funktion wird der Typname durch Typ ersetzt.

Für die min-Funktion erhalten wir Folgendes:

 template<class Type> Type _min(Type a, Type b){ if( a < b){ return a; } return b; } 

Das Interessanteste ist die Tatsache, dass die min-Funktion beim Kompilieren zwar nicht aufgerufen wird, jedoch nicht im Binärcode erstellt (nicht instanziiert ) wird. Wenn Sie eine Gruppe von Funktionsaufrufen mit Variablen verschiedener Typen deklarieren, erstellt jeder Compiler basierend auf der Vorlage eine eigene Implementierung.

Das Aufrufen einer Vorlagenfunktion entspricht im Allgemeinen dem Aufrufen einer normalen Funktion. In diesem Fall bestimmt der Compiler anhand des Typs der tatsächlichen Parameter, welcher Typ anstelle von Type verwendet werden soll. Wenn sich jedoch herausstellt, dass die ersetzten Parameter unterschiedlichen Typs sind, kann der Compiler die Implementierung der Vorlage nicht ausgeben (die Vorlage instanziieren). Im folgenden Code stolpert der Compiler also beim dritten Aufruf, da er nicht bestimmen kann, um welchen Typ es sich handelt (überlegen Sie, warum?):

 #include <iostream> template<class Type> Type _min(Type a, Type b) { if (a < b) { return a; } return b; } int main(int argc, char** argv) { std::cout << _min(1, 2) << std::endl; std::cout << _min(3.1, 1.2) << std::endl; std::cout << _min(5, 2.1) << std::endl; // oops! return 0; } 

Dieses Problem wird gelöst, indem beim Aufrufen der Funktion ein bestimmter Typ angegeben wird.

 #include <iostream> template<class Type> Type _min(Type a, Type b) { if (a < b) { return a; } return b; } int main(int argc, char** argv) { std::cout << _min<double>(5, 2.1) << std::endl; return 0; } 

Wann funktioniert die Vorlagenfunktion (nicht)?


Im Prinzip können Sie verstehen, dass der Compiler einfach den gewünschten Typ in der Vorlage ersetzt. Aber wird die resultierende Funktion immer funktionsfähig sein? Offensichtlich nicht. Jeder Algorithmus kann unabhängig vom Datentyp definiert werden, verwendet jedoch unbedingt die Eigenschaften dieser Daten. Bei der Vorlagenfunktion _min ist dies eine Voraussetzung, um einen Bestelloperator (Operator <) zu definieren.

Jede Funktionsvorlage setzt abhängig von der Implementierung das Vorhandensein bestimmter Eigenschaften eines parametrisierten Typs voraus (z. B. einen Kopieroperator, einen Vergleichsoperator, das Vorhandensein einer bestimmten Methode usw.). Im erwarteten C ++ - Sprachstandard sind Konzepte dafür verantwortlich.

Überlastung der Funktionsvorlage


Funktionsvorlagen können auch überladen werden. Normalerweise wird diese Überlastung ausgeführt, wenn

 template<class Type> Type* _min(Type* a, Type* b){ if(*a < *b){ return a; } return b; } 

Sonderfälle


In einigen Fällen ist die Funktionsvorlage für einen bestimmten Typ unwirksam oder falsch. In diesem Fall können Sie die Vorlage spezialisieren , dh eine Implementierung für diesen Typ schreiben. Im Fall von Zeichenfolgen möchten Sie möglicherweise, dass die Funktion nur die Anzahl der Zeichen vergleicht. Bei einer Spezialisierung der Funktionsvorlage wird im Parameter nicht der Typ angegeben, für den die Vorlage angegeben ist. Das Folgende ist ein Beispiel für diese Spezialisierung.

 template<> std::string _min(std::string a, std::string b){ if(a.size() < b.size()){ return a; } return b; } 

Die Spezialisierung der Vorlage auf bestimmte Typen erfolgt aus wirtschaftlichen Gründen erneut: Wenn diese Version der Funktionsvorlage nicht im Code verwendet wird, wird sie nicht in den Binärcode aufgenommen.
Für die Zukunft bleiben mehrere und ganzzahlige Parameter erhalten. Eine natürliche Erweiterung sind die Klassenvorlagen, die Grundlagen der generativen Programmierung und das Design der C ++ - Standardbibliothek. Und ein paar Beispiele!

Source: https://habr.com/ru/post/de436880/


All Articles