Template-Spezialisierung
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 66 von 104.
Manchmal funktioniert ein generisches Template nicht gut für jeden Typ. Zum Beispiel vergleicht der Vergleich von C-Strings mit > Zeigeradressen und nicht den tatsächlichen Text. Eine Templatespezialisierung ermöglicht es dir, eine benutzerdefinierte implementation für bestimmte Typen bereitzustellen und gleichzeitig die generische Version für alles andere beizubehalten.
Eine full-Spezialisierung ersetzt das gesamte Template für einen bestimmten Typ. Du deklarierst sie mit einer leeren Template-Parameterliste und gibst den konkreten Typ an:
// Primäres Template
template <typename T>
class Printer {
public:
void print(T value) {
std::cout << value << std::endl;
}
};
// Vollständige Spezialisierung für const char*
template <>
class Printer<const char*> {
public:
void print(const char* value) {
std::cout << "String: " << value << std::endl;
}
};
Printer<int> intPrinter;
intPrinter.print(42); // Ausgabe: 42
Printer<const char*> strPrinter;
strPrinter.print("hello"); // Ausgabe: String: helloFunktionsvorlagen können ebenfalls spezialisiert werden:
template <typename T>
bool isEqual(T a, T b) {
return a == b;
}
template <>
bool isEqual<const char*>(const char* a, const char* b) {
return std::strcmp(a, b) == 0;
}
isEqual(5, 5); // Verwendet generische Version
isEqual("hi", "hi"); // Verwendet spezialisierte VersionDer Compiler bevorzugt immer die spezifischste Übereinstimmung – wenn eine Spezialisierung für den genau verwendeten Typ existiert, wird sie gegenüber der generischen Vorlage ausgewählt.
Aufgabe
EinfachLass uns ein typbewusstes Formatierungssystem erstellen, das zeigt, wie du mithilfe von Template-Spezialisierungen das Verhalten für bestimmte Typen anpassen kannst, während du für alle anderen Typen einen generischen Fallback beibehältst.
Du erstellst zwei Dateien, um deinen Template-Code zu organisieren:
Formatter.h: Definiere ein Klassen-Template namensFormatter, das Werte für die Anzeige formatiert. Dein generisches Template sollte mit jedem Typ funktionieren, aber du erstellst außerdem spezialisierte Versionen für Typen, die eine benutzerdefinierte Behandlung benötigen.Das primäre
Formatter-Template sollte Folgendes enthalten:- Eine
format()-Methode, die einen Wert vom Typ T entgegennimmt und Folgendes ausgibt:Value: <value>
Erstelle eine vollständige Spezialisierung für
bool, die menschenlesbaren Text anstelle von 1 oder 0 ausgibt:- Die
format()-Methode sollte Folgendes ausgeben:Boolean: trueoderBoolean: false
Erstelle eine vollständige Spezialisierung für
const char*, die Zeichenketten in Anführungszeichen setzt:- Die
format()-Methode sollte Folgendes ausgeben:String: "<value>"
Erstelle außerdem ein Funktions-Template namens
formatPair, das zwei Werte desselben Typs entgegennimmt und sie gemeinsam wie folgt ausgibt:Pair: [<first>, <second>]Erstelle eine Spezialisierung von
formatPairfürconst char*, die jede Zeichenkette in Anführungszeichen setzt:Pair: ["<first>", "<second>"]- Eine
main.cpp: Lies vier Eingaben ein (jeweils in einer eigenen Zeile):- Eine Ganzzahl
- Eine Gleitkommazahl
- Einen booleschen Wert als Zeichenkette (
trueoderfalse) - Einen Zeichenkettenwert
Führe deine Formatter vor, indem du passende
Formatter-Objekte erstellst und für jeden Typformat()aufrufst:- Formatiere die Ganzzahl mit
Formatter<int> - Formatiere die Gleitkommazahl mit
Formatter<double> - Formatiere den booleschen Wert mit
Formatter<bool>(wandle zuerst die Zeichenketteneingabe in einen booleschen Wert um) - Formatiere die Zeichenkette mit
Formatter<const char*>
Führe anschließend die Spezialisierung des Funktions-Templates vor:
- Rufe
formatPairmit zwei Ganzzahlen auf: 10 und 20 - Rufe
formatPairmit zwei C-Zeichenketten auf:"hello"und"world"
Beispielsweise mit den Eingaben 42, 3.14, true und Hello:
Value: 42
Value: 3.14
Boolean: true
String: "Hello"
Pair: [10, 20]
Pair: ["hello", "world"]Beachte, wie das generische Template Ganzzahlen und Gleitkommazahlen identisch verarbeitet, während die spezialisierten Versionen für bool und const char* eine benutzerdefinierte Formatierung bereitstellen. Der Compiler wählt automatisch für jeden verwendeten Typ die spezifischste passende Variante aus.
Probier es selbst
#include <iostream>
#include <string>
#include "Formatter.h"
using namespace std;
int main() {
// Eingaben lesen
int intVal;
double doubleVal;
string boolStr;
string strVal;
cin >> intVal;
cin >> doubleVal;
cin >> boolStr;
cin >> strVal;
// String in bool umwandeln
bool boolVal = (boolStr == "true");
// TODO: Formatter<int> erstellen und die Ganzzahl formatieren
// TODO: Formatter<double> erstellen und den Double formatieren
// TODO: Formatter<bool> erstellen und den Boolean formatieren
// TODO: Formatter<const char*> erstellen und den String formatieren
// Hinweis: Verwende strVal.c_str(), um const char* zu erhalten
// TODO: formatPair mit zwei Ganzzahlen aufrufen: 10 und 20
// TODO: formatPair mit zwei C-Strings aufrufen: "hello" und "world"
return 0;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamensräume & GültigkeitsbereicheEinführung in OOP in C++Klassen vs. ObjekteDer 'this'-PointerMethoden (Elementfunktionen)Attribute (Datenelemente)Grundlagen zu Ctors & DtorsRückblick - Einfacher Taschenrechner4Klasseneigenschaften
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen & KlassenZusammenfassung - Bankkonto-Manager7Vererbung
Grundlagen der VererbungZugriffsstufen bei VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick - Mitarbeiter-Hierarchie2Speicherverwaltung
Stack vs. Heap SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Smart Pointers in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsspezifizierer in C++Zugriffsspezifizierer im DetailInformation HidingStruct vs. ClassVerschachtelte & innere KlassenRückblick - Studentenverwaltungssystem8Polymorphie
Kompilierzeit- vs. Laufzeit-PolymorphieFunktionsüberladungVirtuelle Funktionen – WiederholungRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIZusammenfassung – Formen-Rechner3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorVerschiebekonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktoren im DetailRule of Three / Five / ZeroZusammenfassung - String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladen der [] und () OperatorenTypumwandlungsoperatorenRückblick - Matrix-Klasse9Templates
FunktionstemplatesKlassentemplatesTemplate-SpezialisierungVariadische TemplatesSFINAE & Type Traits GrundlagenRückblick - Generische ContainerÜbe selbstständig: Online-C++-Compiler