Inhaltsverzeichnis[Anzeigen]

 

Basiswissen zu Templates:

nach C++ Templates von David Vandevoorde und Nicolai M. Josuttis

 

Funktionstemplates:

Beispiel:

template <typename T>

inline T const& max_ (T const& a, T const& b){

return a < b ? b : a;


}

#include <iostream>
#include <string>

using namespace std;

int main (){

cout << "max_(3,4): " << max_(3,4) << endl;

cout << "max_(3.5,3.6): " << max_(3.5,3.6) << endl;

cout << "max_(string(\"abc\"), string(\"cdf\") : " << max_(string("abc"), string("cdf") ) << endl;
cout << "max_(\"abc\", \"cdf\") : " << max_("abc","cdf") << endl;

//cout << "max_( 1, 2.2 ): " << max_(1, 2.2) << endl;
cout << "max_< double > (1,2.2): " << max_< double > (1,2.2) << endl;

cout << "max_(static_cast< double > (1),2.2): " << max_(static_cast < double >(1),2.2) << endl;
cout << "max_( new int, new int ): " << max_( new int, new int ) << endl;


}
  • die Funktionenfamilie wird durch den Typ T bestimmt
  • template <typename T> ist äquivalent zu template <class T>
  • jeder Typ kann verwendet werden, der den Operator < unterstützt
  • die Ersetzung des Templateparameters T durch ein Templatetype wird als Templateinstanzierung bezeichnet
  • für jeden konkreten Typ (double) wird die Templatefunktion zu:
inline double const& max_ (double const& a, double const& b){ 

return a < b ? b : a;
  • Templates werden zwei Mal auf syntaktische Richtigkeit geprüft
    • der Templatecode im Allgemeinen
    • die Templateinstanzierung im Besonderen (kann der Typ als Templateparameter verwendet werden)
    • Konsequenzen:
      • eine Templateinstanzierung kann nur erfolgen, wenn sie ihre Templatedefinition kennt
      • das klassische Trennung von Übersetzen und Linken ist aufgehoben, denn "klassisch" genügt zum Übersetzen die Funktionsdeklaration
      • Templates müssen im Header definiert werden

Templateparameter und Aufrufparameter :

  • T in template < typename T > wird als Templateparameter bezeichnet
  • a und b in max_( T const& a, T const& b ) nennt man Aufrufparameter "call parameters"
  • Funktionstemplates kennen im Gegensatz zu Klassentemplates keine Defaultparameter, da sich, historisch bedingt, Templateparameter aus den Aufrufargumenten ableiten lassen sollten right max_( T const& = int ) ist noch nicht möglich )
  • Lösung des Codeproblems mittels zwei Templateparametern:
template <typename T1, typename T2>

inline T1 max_ (T1 const& a, T2 const& b){

return a < b ? b : a;

}
  • Nachteile:
    • max_( 42, 66.6 ) = 66 =! 66.6 = max_( 66.6, 42 )
    • der zweite Aufrufargument muss gegenfalls konvertiert werden right lokales temporäres Objekt geht "out of scope" beim Verlassen der max_ Funktion right Rückgabe durch kopieren
  • das Konzept zur Parameterbestimmung: Aufrufargumente right Templateparameter right Aufrufparameter
  • Versuch mit drei Templateparametern:
template <typename T1, typename T2, typename RT>

inline RT max_ (T1 const& a, T2 const& b){

return a < b ? b : a;

}
  • Nachteil: der Aufruf ist mühsam max_<int,double,double>(4,4.2)
  • Umstellung der Templateparameter:
template <typename RT, typename T1, typename T2>

inline RT max_ (T1 const& a, T2 const& b){

return a < b ? b : a;

}
  • Vorteil: kann mittels max_<double>(4,4.2) instanziert werden, da die Aufrufargumente die restlichen Templateparameter und Aufrufparameter bestimmen

Template Funktionen überladen:

  • Überladen bezeichnet die Möglichkeit, mehrere Funktionen mit gleichem Namen aber unterschiedlichen Aufrufparametern zu definieren, sodass der Compiler entscheiden muss, welche Funktion er aufzurufen hat
  • die Aufrufparameter werden auch gern als Signatur einer Funktion bezeichnet
  • ein paar Beispiele:
// max von zwei ints (1)
inline int const& max_(int const& a, int const& b){

return a < b ? b : a;

}

// max von zwei beliebigen, gleichen Typen (2)
template <typename T>
inline T const& max_ (T const& a, T const& b)

{
return a < b ? b : a;
}

// max von drei beliebigen Typen (3)
template <typename T>
inline T const& max_ (T const& a, T const& b, T const& c)

{
return max_(max_(a,b), c);

}

int main()
{
max_(7, 42, 68);
// 3 mit int

max_(7.0, 42.0);
// 2 mit double
max_('a', 'b');
// 2 mit char

max_(7, 42);
// 1 mit int
max_<>(7, 42);

// 2 mit int
max_<double>(7, 42);
// 2 mit double
max_('a', 42.7);
// 1 mit int

}
  • Funktionen können mit Funktionstemplates koexistieren
  • Funktionstemplates unterstützen keine automatische Typekonvertierung im Gegensatz zu Funktionen (max('a',42.7 ist nur mittels einer Funktion möglich)
  • "rule of thumb" zur Funktionenauswahl: die am meisten spezialisierte Funktion wird ausgewählt
    • Funktionen werden Funktionstemplates vorgezogen ( max_(7, 42) => 1 )
    • wenn das Funktionstemplate besser passt als die Funktion, wird das Funktionstemplate vorgezogen (max_('a','b')) right 2 nicht 1, da keine Typekonvertierung notwendig
  • durch günstige Auswahl der Aufrufparameter, kann die Anzahl der Templateinstanziierungen reduziert werden:
// max von zwei Pointern auf den gleichen Typ
template <typename T>

inline T* const& max (T* const& a, T* const& b){

return *a < *b ? b : a;

}

Klassentemplates

  • Container aus der Standard Template Library (STL) sind typische Beispiele für Klassentemplates

Beispiel:

template <typename T>
class Stack {
private:

std::vector<T> elems;

public:
Stack();
Stack( Stack<T> const& );
Stack>T>& operator= (Stack<T> const&);

void push(T const&);
void pop();
T top() const;
bool empty() const;

};
  • die Stackmethoden werden dann wie Funktionstemplates definiert right die Regeln für Funktionstemplates gelten natürlich auch hier;
template <typename T>

bool Stack<T>::empty() const{ ... }
  • Instanzierung der Memberfunktionen:
    • die Memberfunktionen der Klassentemplates werden dann instanziert, wenn sie verwendet werden:
template <typename T>

class Foo{
public:
T t;
void set( const T& val ){ t= val.t; }

T operator*( const& T val){ return t*val.t ; }

};

int main(){
Foo< std::string > fooString;

Foo< std::string > barString;
fooString.set(std::string("foo");

barString.set(std::string("bar");
// die nächste Zeile führt zum Compilefehler
Foo < std::string> fooBarString= fooString* barString;

}
  • Vorteile:
    • schnellers Übersetzen
    • weniger Code wird instanziert
    • Templateklassen können mit Typen T instanziert werden, die nicht alle Methoden des Klassentemplates unterstützen
    Nachteile:
    • Fehler zur Laufzeit des Programms

Spezialisierung von Templateklassen:

  • ähnlich zu Funktionstemplates kann man auch Klassentemplates für bestimmte Typen spezialisieren
    • Klassentemplates spezialisieren right nun müssen auch alle Memberfunktionen spezialisiert werden
    • Memberfunktionen spezialisieren right nun kann die Templateklasse als ganzes nicht mehr spezialiert werden
  • Beispiel zur Spezialisierung der Klassentemplates
template <typename T > 
class Stack{

...
};
template <typename T>
T Stack<T>::top() const {

...
}
  • wird für die Spezialisierung std::string
template <>

class Stack<std::string>{
...
};
T Stack<std::string>::top(){

...
}
  • Beispiel zur Spezialisierung der Memberfunktionen für std::string:
template <typename T > 

class Stack{

T top() const;

template <>

T top<std::string>() const;

};

template <typename T>

T Stack<T>::top const{
...
}

template <typename T>

template <>
T Stack<T>::top<std::string>() const{
...

}

 

Partielle Spezialisierung:

  • Template Parameter können im Gegensatz zum vorherigen Punkt teilweise spezialisiert werden:
  • allgemeine Template Parameter (1):
template <typename T1, typename T2>
class MyClass{

...
};

 

  • gleiche Template Parameter (2):

template <typename T>

class MyClass<T,T>{
...
};
  • zweiter Template Parameter ist int (3):
template <typename T>
class MyClass<T,int>{

...
};
  • beide Templateparameter sind Pointer (4):
template <typename T1, typename T2>

class MyClass<T1*,T2*>{
...
};
  • Anwendung:
MyClass<int,float> a; // (1)
MyClass<float,float> a; // (2)

MyClass<float,int> a; // (3)
MyClass<int*,float*> a; // (4)

MyClass<int,int>; // Fehler, da nicht eindeutig (2) versus (3)
MyClass<int*,int*>; // Fehler, da nicht eindeutig (2) versus (4)
  • mögliche Auflösung der Fehler:
template <>
class MyClass<int,int>{

...
};
template <typename T>
class MyClass<T*,T*>{

...
};
  • partielle Spezialisierung ist ein mächtiges Werkzeug für neue Programmiertechniken: Static Metaprogramming und Expression Templates
// berechne 3^N

template<int N>
class Pow3 {
public:
enum { result = 3 * Pow3<N-1>::result };

};

template<>
class Pow3<0> {
public:

enum { result = 1 };
};

int main()
{
std::cout << "Pow3<7>::result = " << Pow3<7>::result << '\n';

}
  • Static Metaprogramming heißt diese Technik, weil zur Compilzeit (static) Code erzeugt und evaluiert wird (Metaprogramming)
  • auch Werte können Templateparameter sein
  • es gilt: Pow3<7>::result = 3 * Pow3<6>::result = 3 * 3 * Pow3<6>::result = ... = 3 * 3 * 3 * 3 * 3 * 3 * 3 * Pow3<0> = 3 * 3 * 3 * 3 * 3 * 3 * 3 * 1
  • Expression Templates stammen aus der Matrixarithmtik
  • so kann mittels Expression Templates ein Matrixoperation so geschickt aufgesplittet werden, daß folgende Operation statt 6000 Lese- und 4000 Schreiboperatone nur noch 2000 Lese- und 1000 Schreiboperationen benötigt
Array<double> x(1000),y(1000);

x= 1.2*x + x*y;
  • beide Techniken werden gerade in neueren C++ Bibliotheken (Blitz++, Boost) exzessiv verwendet

Default Template Argumente:

  • im Gegensatz zu Funktionstemplate kennen Klassentemplates default Argumente
  • Prominentes Beispiel:
template<class Ch, class Tr= char_traits<Ch>, class A= allocator<Ch> >

class std::basic_string{
...
};
typedef basic_string<char> string;

typedef basic_string<wchar_t> wstring;
int main(){
std::string str; // entspricht std::basic_string<char> str

basic_string<char,myCharTrait, myCharAllocator> myString;
}
  • Flexibilität wird hier durch das Traits-Pattern erreicht

"Nontype" Template Parameter:

  • Template Parameter können auch gewöhnliche Werte sein
  • die Templateklasse wird dann nicht mit einem Typ, sondern mit einem Wert initialisiert

Templateklassen Parameter:

  • Beispiel:
template <typename T, int MAXSIZE>

class Stack {
private:
T elems[MAXSIZE];
int numElems;

public:
Stack();
void push(T const&);

void pop();
T top() const;
bool empty() const {

return numElems == 0;
}
bool full() const {

return numElems == MAXSIZE;
}
};
...

int main(){

Stack<int,20> int20Stack;
Stack<int,40> int40Stack;

Stack<std::string,40> stringStack;
}
  • jede Template Instanzierung stellt einen eigenen Typ dar
  • d.h.: keine implizite cast int20Stack=int40Stack oder explizite cast int20Stack= static_cast< int20Stack>(int40Stack) sind möglich
  • default Wert sind auch zulässig:
template <typename T = int, int MAXSIZE = 200>

class Stack{
...
};

Funktionstemplate Parameter:

  • typisches Beispiel in der STL:
template <typename T, int VAL>

T addValue (T const& x){
return x + VAL;

}
...
int main(){
...
std::transform ( source.begin(), source.end() ,

dest.begin(),
addValue<int,5>);
};

Einschränkungen:

  • Floating-point Zahlen, Klassentypen und Objekte mit interne Bindung sind als Templateparameter nicht erlaubt:
template <double VAT>

double process( double v){
return v * VAT;

}
template <std::string name >
class MyClass{
...
};

template <char const* name>
class MyClass{
...
};
  • Floating-point Zahlen werden wohl im nächsten Standard unterstützt werden
  • Objekte mit interner Bindung (lokale Objekte) wie String Literale, die erst zur Laufzeit bekannt sind, können nicht zur Compilezeit zum Instanziieren verwendet werden

Mentoring

Stay Informed about my Mentoring

 

Rezensionen

Tutorial

Besucher

Heute 913

Gestern 3357

Woche 12476

Monat 39793

Insgesamt 3892507

Aktuell sind 40 Gäste und keine Mitglieder online

Kubik-Rubik Joomla! Extensions

Abonniere den Newsletter (+ pdf Päckchen)

Beiträge-Archiv

Sourcecode

Neuste Kommentare