Klassen-Templates
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 65 von 104.
So wie Funktionstemplates es dir ermöglichen, typspezifische Funktionen zu schreiben, kannst du mit Klassentemplates typspezifische Klassen erstellen. So funktionieren Container wie std::vector: Du definierst die Struktur einmal, und der Compiler erzeugt für jeden verwendeten Typ eine eigene Version.
template <typename T>
class Box {
T value;
public:
Box(T v) : value(v) {}
T getValue() const { return value; }
void setValue(T v) { value = v; }
};
int main() {
Box<int> intBox(42);
Box<std::string> strBox("Hello");
std::cout << intBox.getValue() << std::endl; // 42
std::cout << strBox.getValue() << std::endl; // Hello
}Beim Erstellen von Objekten aus Klassenvorlagen gibst du den Typ normalerweise in spitzen Klammern an. Seit C++17 kann der Compiler ihn auch aus den Konstruktorargumenten ableiten (Box b(42); erstellt ein Box<int>), aber wenn keine Argumente vorhanden sind, aus denen er ihn ableiten kann, musst du den Typ angeben. Der Compiler erzeugt dann eine vollständige Klassendefinition für diesen spezifischen Typ.
Klassenvorlagen können mehrere Typparameter und sogar Nicht-Typparameter wie Ganzzahlen haben:
template <typename T, int Size>
class FixedArray {
T data[Size];
public:
T& operator[](int index) { return data[index]; }
int size() const { return Size; }
};
FixedArray<double, 5> arr; // Array aus 5 double-Werten
arr[0] = 3.14;Wenn du Member-Funktionen außerhalb der Klasse definierst, musst du die Template-Deklaration wiederholen:
template <typename T>
class Container {
T* data;
public:
Container();
~Container();
};
template <typename T>
Container<T>::Container() : data(nullptr) {}
template <typename T>
Container<T>::~Container() { delete data; }Klassenvorlagen bilden die Grundlage der generischen Programmierung in C++ und ermöglichen es dir, wiederverwendbare Datenstrukturen zu schreiben, die mit jedem Typ funktionieren.
Aufgabe
EinfachLass uns mithilfe von Klassenvorlagen ein generisches Speichersystem erstellen, das einen flexiblen Container für beliebige Datentypen bereitstellt. Du organisierst deine Vorlagenklasse in einer Headerdatei und zeigst ihre Vielseitigkeit mit verschiedenen Typen in deinem Hauptprogramm.
Du erstellst zwei Dateien:
Storage.h: Definiere eine Klassenvorlage namensStorage, die als einfacher Container für einen einzelnen Wert mit einigen nützlichen Operationen dient:Deine
Storage-Vorlage sollte Folgendes enthalten:- Ein privates Element zum Speichern des Werts
- Einen Konstruktor, der einen Anfangswert entgegennimmt
- Eine Methode
getValue(), die den gespeicherten Wert zurückgibt - Eine Methode
setValue(), die den gespeicherten Wert aktualisiert - Eine Methode
isEmpty(), dietruezurückgibt, wenn der Wert dem standardmäßig konstruierten Wert des Typs T entspricht, andernfallsfalse
Erstelle außerdem eine Klassenvorlage namens
Pairmit zwei Typparametern, die zwei zusammengehörige Werte speichert:- Private Elemente für den ersten und den zweiten Wert (die möglicherweise unterschiedliche Typen haben)
- Einen Konstruktor, der beide Werte initialisiert
- Die Methoden
getFirst()undgetSecond(), um die jeweiligen Werte abzurufen - Eine Methode
display(), die Folgendes ausgibt:(<first>, <second>)
main.cpp: Lies vier Eingaben ein (jeweils in einer separaten Zeile):- Einen ganzzahligen Wert
- Einen neuen ganzzahligen Wert
- Einen Zeichenkettenwert
- Einen double-Wert
Führe mit deinen Vorlagen Folgendes vor:
- Erstelle ein
Storage<int>mit der ersten Ganzzahl und gib Folgendes aus:Int storage: <value> - Aktualisiere es mithilfe von
setValue()mit der zweiten Ganzzahl und gib anschließend Folgendes aus:Updated: <value> - Erstelle ein
Storage<std::string>mit der eingegebenen Zeichenkette und gib Folgendes aus:String storage: <value> - Prüfe, ob der Zeichenkettenspeicher leer ist, und gib Folgendes aus:
Is empty: <true/false>(gibtrueoderfalseaus) - Erstelle ein
Pair<std::string, double>mit der Zeichenkette und dem double-Wert und rufe anschließenddisplay()auf - Erstelle ein
Pair<int, int>mit beiden ganzzahligen Eingaben und rufe anschließenddisplay()auf
Zum Beispiel bei den Eingaben 42, 100, Hello und 3.14:
Int storage: 42
Updated: 100
String storage: Hello
Is empty: false
(Hello, 3.14)
(42, 100)Beachte, wie dieselbe Storage-Vorlage nahtlos mit Ganzzahlen und Zeichenketten funktioniert und wie Pair unterschiedliche Typen miteinander kombinieren kann. Der Compiler erzeugt für jede verwendete Typkombination separate Klassendefinitionen. Denke daran, für Vorlagen mit einem einzelnen Typ die Syntax template <typename T> und für Vorlagen mit mehreren Typparametern template <typename T, typename U> zu verwenden.
Probier es selbst
#include <iostream>
#include <string>
#include "Storage.h"
using namespace std;
int main() {
// Eingaben einlesen
int intValue1;
int intValue2;
string strValue;
double doubleValue;
cin >> intValue1;
cin >> intValue2;
cin >> strValue;
cin >> doubleValue;
// TODO: Storage<int> mit der ersten Ganzzahl erstellen und ausgeben: Int storage: <value>
// TODO: Mit der zweiten Ganzzahl über setValue() aktualisieren und ausgeben: Updated: <value>
// TODO: Storage<std::string> mit der Zeichenketteneingabe erstellen und ausgeben: String storage: <value>
// TODO: Prüfen, ob der String-Speicher leer ist, und ausgeben: Is empty: true/false
// TODO: Pair<std::string, double> mit der Zeichenkette und dem double-Wert erstellen und display() aufrufen
// TODO: Pair<int, int> mit beiden Ganzzahlen erstellen und display() aufrufen
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