Klassen-Templates
Ein Funktions-Template reprÀsentiert eine Familie von Funktionen. Entsprechend reprÀsentiert ein Klassen-Template eine Familie von Klassen. Heute möchte ich Klassen-Templates vorstellen.
Ein Funktions-Template reprÀsentiert eine Familie von Funktionen. Entsprechend reprÀsentiert ein Klassen-Template eine Familie von Klassen. Heute möchte ich Klassen-Templates vorstellen.
Ein Klassen-Template zu definieren ist einfach.
Definition eines Klassen-Templates
Angenommen, du hast eine Klasse Array, die ein Klassen-Template werden soll.
class Array{
public:
int getSize() const {
return 10;
}
private:
int elem[10];
};
Die Klasse Array enthĂ€lt ein C-Array vom Typ int mit der LĂ€nge 10. Der Typ des C-Arrays und seine LĂ€nge sind offensichtliche Erweiterungspunkte. Lass mich ein Klassen-Template erstellen, indem wir einen Typ-Parameter T und einen Nicht-Typ-Parameter N einfĂŒhren und damit spielen.
// arrayClassTemplate.cpp
#include <cstddef> // (1)
#include <iostream>
#include <string>
template <typename T, std::size_t N> // (2)
class Array{
public:
std::size_t getSize() const {
return N;
}
private:
T elem[N]
};
int main() {
std::cout << '\n';
Array<int, 100> intArr; // (3)
std::cout << "intArr.getSize(): " << intArr.getSize() << '\n';
Array<std::string, 5> strArr; // (4)
std::cout << "strArr.getSize(): " << strArr.getSize() << '\n';
Array<Array<int, 3>, 25> intArrArr; // (5)
std::cout << "intArrArr.getSize(): " << intArrArr.getSize() << '\n';
std::cout << '\n';
}
Das Array wird durch seinen Typ und seine GröĂe parametrisiert. FĂŒr die GröĂe habe ich den vorzeichenlosen Integer-Typ std::size_t (2) verwendet, der die maximale GröĂe speichern kann. Um std::size_t zu verwenden, muss ich den Header <cstddef> (1) einbinden. Jetzt kann das Array mit einem int (3), einem std::string (4) und einem Array<int, 3> (5) instanziiert werden. Der folgende Screenshot zeigt die Ausgabe des Programms.
Du kannst die Memberfunktionen eines Templates innerhalb und auĂerhalb des Klassen-Templates definieren.
Definitionen der Memberfunktionen
Die Definition der Member-Funktionen innerhalb des Klassen-Templates ist ganz intuitiv.
template <typename T, std::size_t N>
class Array{
public:
std::size_t getSize() const {
return N;
}
private:
T elem[N]
};
Wenn du die Memberfunktionen auĂerhalb der Klasse definierst, musst Du angeben, dass es sich um ein Template handelt. Dazu ist es notwendig, die volle Typqualifikation des Klassen-Templates anzugeben. Das modifizierte Klassen-Template Array sieht dann so aus:
template <typname T, std::size_t N>
class Array{
public:
std::size_t getSize() const;
private:
T elem[N]
};
template <typename T, std::size_t N> // (1)
std::size_t Array<T, N>::getSize() const {
return N;
}
(1) ist die Memberfunktion getSize des Arrays, die auĂerhalb der Klasse definiert ist. Die Memberfunktion auĂerhalb des Klassen-Templates zu definieren, wird aufwĂ€ndig, wenn die Memberfunktion selbst ein Template ist.
Memberfunktionen als Templates
Ein typisches Beispiel fĂŒr eine generische Memberfunktion ist ein generischer Zuweisungsoperator. Der Grund fĂŒr einen generischen Zuweisungsoperator ist naheliegend. Ein Array<T, N> soll sich einem Array<T2, N2> zuweisen lassen, wenn der Datentyp T sich T2 zuweisen lĂ€sst und beide Arrays die gleiche GröĂe besitzen.
Das Zuweisen eines Array<float, 5> an ein Array<double, 5> ist nicht gĂŒltig, da beide Arrays unterschiedliche Typen besitzen.
// arrayAssignmentError.cpp
#include <cstddef>
#include <iostream>
#include <string>
template <typename T, std::size_t N>
class Array{
public:
std::size_t getSize() const {
return N;
}
private:
T elem[N]
};
int main() {
std::cout << '\n';
Array<float, 5> floatArr;
Array<float, 5> floatArr2;
floatArr2 = floatArr; // (1)
Array<double, 5> doubleArr;
doubleArr = floatArr; // (2)
}
Das Zuweisen von floatArr an floatArr2 (1) ist gĂŒltig, da beide Arrays den gleichen Typ haben. Das Zuweisen von floatArr an doubleArr ist hingegen nicht gĂŒltig (2). Der Compiler beschwert sich, dass es keine Konvertierung von Array<float, 5> zu einem Array<double, 5> gibt.
Hier ist eine naive Implementierung der Klasse Array, die die Zuweisung von zwei Arrays gleicher LĂ€nge unterstĂŒtzt. Das C-Array elem ist absichtlich public.
template <typename T, std::size_t N>
class Array{
public:
template <typename T2>
Array<T, N>& operator = (const Array<T2, N>& arr) {
std::copy(std::begin(arr.elem), std::end(arr.elem), std::begin(elem));
return *this;
}
std::size_t getSize() const {
return N;
}
T elem[N];
};
Der Zuweisungsoperator Array<T, N>& operator = (const Array<T2, N>& arr) akzeptiert Arrays, die im zugrundeliegenden Typ variieren können, aber nicht in der LÀnge. Bevor ich den vollstÀndigen Code vorstelle, möchte ich ihn noch sukzessive verbessern.
Freundschaft
Wenn elem als private definiert wird, muss Array zum Freund der Klasse werden.
template <typename T, std::size_t N>
class Array{
public:
template <typename T2>
Array<T, N>& operator = (const Array<T2, N>& arr) {
std::copy(std::begin(arr.elem), std::end(arr.elem), std::begin(elem));
return *this;
}
template<typename, std::size_t> friend class Array; // (1)
std::size_t getSize() const {
return N;
}
private:
T elem[N]
};
Die Zeile template<typename, std::size_t> friend class Array (1) erklÀrt alle Instanzen von Array zu Freunden.
Memberfunktionen auĂerhalb der Klasse definiert
Die generische Memberfunktion auĂerhalb der Klasse zu definieren ist ein wenig mĂŒhsam.
template <typename T, std::size_t N>
class Array{
public:
template <typename T2>
Array<T, N>& operator = (const Array<T2, N>& arr);
template<typename, std::size_t> friend class Array;
std::size_t getSize() const;
private:
T elem[N]
};
template <typename T, std::size_t N>
std::size_t Array<T, N>::getSize() const { return N; }
template<typename T, std::size_t N> // (1)
template<typename T2>
Array<T, N>& Array<T, N>::operator = (const Array<T2, N>& arr) {
std::copy(std::begin(arr.elem), std::end(arr.elem), std::begin(elem));
return *this;
}
Es ist fĂŒr eine auĂerhalb des Klassenkörpers definierte generische Memberfunktion (1) notwendig, dass die Klasse und die Memberfunktionen Templates sind. ZusĂ€tzlich muss die vollstĂ€ndige Typqualifikation der generischen Memberfunktion angeben werden. In der Klasse Array wird der Zuweisungsoperator auch fĂŒr Datentypen T und T2 eingesetzt, die nicht konvertierbar sind. Das Aufrufen des Zuweisungsoperators mit nichtkonvertierbaren Typen fĂŒhrt natĂŒrlich zu einer "hĂ€sslichen" Fehlermeldung. Dieses Problem sollte ich beheben.
Anforderungen an die Typparameter
Die Anforderungen lassen sich mit der type traits [1] library und static_assert (C++11) oder mit Concepts (C++20) formulieren. Hier sind die beiden Varianten des generischen Zuweisungsoperators:
- C++11
template<typename T, std::size_t N>
template<typename T2>
Array<T, N>& Array<T, N>::operator = (const Array<T2, N>& arr) {
static_assert(std::is_convertible<T2, T>::value, // (1)
"Cannot convert the source type into the destination type!");
std::copy(std::begin(arr.elem), std::end(arr.elem), std::begin(elem));
return *this;
}
- C++20
AbschlieĂend ist hier das komplette Programm unter Verwendung des Concepts std::convertible_to in der Deklaration (1) und der Definition (2) der Memberfunktion.
// arrayAssignment.cpp
#include <algorithm>
#include <cstddef>
#include <iostream>
#include <string>
#include <concepts>
template <typename T, std::size_t N>
class Array{
public:
template <typename T2>
Array<T, N>& operator = (const Array<T2, N>& arr) requires std::convertible_to<T2, T>; // (1)
template<typename, std::size_t> friend class Array;
std::size_t getSize() const;
private:
T elem[N];
};
template <typename T, std::size_t N>
std::size_t Array<T, N>::getSize() const { return N; }
template<typename T, std::size_t N>
template<typename T2>
Array<T, N>& Array<T, N>::operator = (const Array<T2, N>& arr) requires std::convertible_to<T2, T> { // (2)
std::copy(std::begin(arr.elem), std::end(arr.elem), std::begin(elem));
return *this;
}
int main() {
std::cout << '\n';
Array<float, 5> floatArr;
Array<float, 5> floatArr2;
floatArr.getSize();
floatArr2 = floatArr;
Array<double, 5> doubleArr;
doubleArr = floatArr;
Array<std::string, 5> strArr;
// doubleArr = strArr; // (3)
}
Wenn ich den Ausdruck (3) verwende, beschwert sich der GCC im Wesentlichen darĂŒber, dass die EinschrĂ€nkungen nicht erfĂŒllt werden.
Wie geht es weiter?
NatĂŒrlich bin ich noch nicht fertig mit meinen Artikeln zu Klassen-Templates. In meinem nĂ€chsten Artikel schreibe ich ĂŒber zwei knifflige Details: Vererbung von Klassen-Templates und die Instanziierung von Memberfunktionen von Klassen-Templates
The Next PDF-Bundle
I want to resuscitate an old service and create bundles about old posts. I will create the bundles only for my English posts because this is quite a job. These bundles include the posts, all source files, and a cmake file. In order for me to make the right decision, you have to make your cross. I will build the pdf bundle with the most votes. The vote is open until 30th of May (including). Vote here [2]. ( [3])
URL dieses Artikels:
https://www.heise.de/-6052426
Links in diesem Artikel:
[1] https://en.cppreference.com/w/cpp/header/type_traits
[2] https://www.modernescpp.com/index.php/which-pdf-bundle-do-you-want-make-your-choice
[3] mailto:rainer@grimm-jaud.de
Copyright © 2021 Heise Medien