Template-Spezialisierung
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 66 von 104.
Manchmal funktioniert eine generische Vorlage nicht gut für jeden Typ. Zum Beispiel vergleicht das Vergleichen von C-Strings mit > Zeigeradressen und nicht den eigentlichen Text. Die Templatespezialisierung ermöglicht es dir, eine benutzerdefinierte Implementierung für bestimmte Typen bereitzustellen und gleichzeitig die generische Version für alles andere beizubehalten.
Eine vollständige 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
EinfachErstellen wir ein typsicheres Formatierungssystem, das zeigt, wie die Template-Spezialisierung das Anpassen des Verhaltens für bestimmte Typen ermöglicht und gleichzeitig für alle anderen Typen eine generische Standardimplementierung beibehält.
Du erstellst zwei Dateien, um deinen Template-Code zu organisieren:
Formatter.h: Definiere ein Klassentemplate namensFormatter, das Werte zur Anzeige formatiert. Dein generisches Template sollte mit jedem Typ funktionieren. Zusätzlich erstellst du 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 übernimmt und Folgendes ausgibt:Value: <value>
Erstelle eine vollständige Spezialisierung für
bool, die anstelle von 1 oder 0 einen für Menschen lesbaren Text ausgibt:- Die
format()-Methode sollte Folgendes ausgeben:Boolean: trueoderBoolean: false
Erstelle eine vollständige Spezialisierung für
const char*, die Zeichenfolgen in Anführungszeichen setzt:- Die
format()-Methode sollte Folgendes ausgeben:String: "<value>"
Erstelle außerdem ein Funktionstemplate namens
formatPair, das zwei Werte desselben Typs übernimmt und sie zusammen alsPair: [<first>, <second>]ausgibt.Erstelle eine Spezialisierung von
formatPairfürconst char*, die jede Zeichenfolge in Anführungszeichen setzt:Pair: ["<first>", "<second>"]- Eine
main.cpp: Lies vier Eingaben ein, jeweils in einer eigenen Zeile:- Eine Ganzzahl
- Eine Gleitkommazahl vom Typ double
- Einen booleschen Wert als Zeichenfolge (
trueoderfalse) - Einen Zeichenfolgenwert
Demonstriere deine Formatierer, 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 die Eingabezeichenfolge zuerst in einen booleschen Wert um) - Formatiere die Zeichenfolge mit
Formatter<const char*>
Demonstriere anschließend die Spezialisierung des Funktionstemplates:
- Rufe
formatPairmit zwei Ganzzahlen auf: 10 und 20 - Rufe
formatPairmit zwei C-Zeichenfolgen auf:"hello"und"world"
Bei den Eingaben 42, 3.14, true und Hello sieht die Ausgabe beispielsweise so aus:
Value: 42
Value: 3.14
Boolean: true
String: "Hello"
Pair: [10, 20]
Pair: ["hello", "world"]Beachte, wie das generische Template Ganzzahlen und Gleitkommazahlen identisch behandelt, 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 Version 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 & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer ContainerÜbe selbstständig: Online-C++-Compiler