C++ Core Guidelines: Template-Definitionen
Template-Definitionen beschĂ€ftigen sich mit Regeln, die typisch fĂŒr die Implementierung eines Templates sind. Das bedeutet insbesondere auch, wie stark die Template-Definition von ihrem Kontext abhĂ€ngt.
Template-Definitionen beschĂ€ftigen sich mit Regeln, die typisch fĂŒr die Implementierung eines Templates sind. Das bedeutet insbesondere auch, wie stark die Template-Definition von ihrem Kontext abhĂ€ngt.
Hier sind die Regeln, um die es im heutigen Artikel geht:
- T.60: Minimize a templateâs context dependencies [1]
- T.61: Do not over-parameterize members (SCARY) [2]
- T.62: Place non-dependent class template members in a non-templated base class [3]
Mit der ersten Regel geht es bereits recht speziell los:
T.60: Minimize a templateâs context dependencies [4]
Ich habe einige Momente benötigt, bis ich die Regel verstanden habe. Ein Blick auf die Funktions-Templates sort und algo hilft. Dies ist das vereinfachte Beispiel aus den Guidelines:
template<typename C>
void sort(C& c)
{
std::sort(begin(c), end(c)); // necessary and useful dependency
}
template<typename Iter>
Iter algo(Iter first, Iter last) {
for (; first != last; ++first) {
auto x = sqrt(*first); // potentially surprising dependency: which sqrt()?
helper(first, x); // potentially surprising dependency:
// helper is chosen based on first and x
}
Es wĂ€re optimal, ist aber nicht immer erreichbar, dass ein Template lediglich seine Argumente verwendet. Dies gilt fĂŒr das Funktions-Template sort; aber nicht fĂŒr algo. Das Funktions-Template algo besitzt AbhĂ€ngigkeiten zu den Funktionen sqrt und helper. Letztlich fĂŒhrt die Implementierung von algo mehr AbhĂ€ngigkeiten ein, als es sich aus dem Interface ablesen lĂ€sst.
T.61: Do not over-parameterize members (SCARY) [5]
Falls ein Mitglied eines Templates nicht von Template-Parametern abhĂ€ngt, entferne es aus dem Template. Ein Mitglied kann ein Datentyp oder eine Methode sein. Indem du diese Regel anwendest, verringert sich die CodegröĂe, denn nichtgenerischer Code ist nicht Bestandteil des Templates.
Das Beispiel aus den Guidelines ist leicht zu verstehen:
template<typename T, typename A = std::allocator{}>
// requires Regular<T> && Allocator<A>
class List {
public:
struct Link { // does not depend on A
T elem;
T* pre;
T* suc;
};
using iterator = Link*;
iterator first() const { return head; }
// ...
private:
Link* head;
};
List<int> lst1;
List<int, My_allocator> lst2;
Der Datentyp Link hÀngt nicht vom Template-Parameter A ab. Daher kann ich ihn entfernen und in List2 direkt verwenden:
template<typename T>
struct Link {
T elem;
T* pre;
T* suc;
};
template<typename T, typename A = std::allocator{}>
// requires Regular<T> && Allocator<A>
class List2 {
public:
using iterator = Link<T>*;
iterator first() const { return head; }
// ...
private:
Link* head;
};
List<int> lst1;
List<int, My_allocator> lst2;
Dies war einfach? Ja? Nein? Die Regel verwendet die AbkĂŒrzung SCARY. FĂŒr was steht sie? Zumindest bin ich neugierig. Aber um ehrlich zu sein, kannst du die nĂ€chsten Zeilen ignorieren.
Das Akronym steht fĂŒr "describes assignments and initializations that are Seemingly erroneous (appearing Constrained by conflicting generic parameters), but Actually work with the Right implementation (unconstrained bY the conflict due to minimized dependencies)".
Die Details dazu gibt es im Dokument N2911. [6] Um dich nicht zu langweilen, hier ist die Idee vereinfacht auf die Container der Standard Template Libray angewandt: Diese besitzen keine AbhÀngigkeiten zu ihren Datentypen key_compare, hasher, key_equal oder allocator und können damit unabhÀngig erweitert werden.
Die nĂ€chste Regel hilft, um die CodegröĂe zu reduzieren.
T.62: Place non-dependent class template members in a non-templated base class [7]
Lass es mich einfacher ausdrĂŒcken: Verschiebe die FunktionalitĂ€t eines Templates, die nicht von den Template-Parametern abhĂ€ngt, in eine Basisklasse, die kein Template ist.
Zu dieser Regel bieten die Guidelines ein einfaches Beispiel an:
template<typename T>
class Foo {
public:
enum { v1, v2 };
// ...
};
Die AufzÀhlung hÀngt nicht vom Typparameter T ab und sollte daher in eine nichtgenerische Basisklasse verschoben werden:
struct Foo_base {
enum { v1, v2 };
// ...
};
template<typename T>
class Foo : public Foo_base {
public:
// ...
};
Jetzt lÀsst sich Foo ohne Template-Argumente und Template-Instanziierung verwenden.
Diese Technik ist interessant, wenn du die CodegröĂe reduzieren willst. Dazu habe ich ein einfaches Klassen-Template Array implementiert:
// genericArray.cpp
#include <cstddef>
#include <iostream>
template <typename T, std::size_t N>
class Array{
public:
Array()= default;
std::size_t getSize() const{
return N;
}
private:
T elem[N];
};
int main(){
Array<int, 100> arr1;
std::cout << "arr1.getSize(): " << arr1.getSize() << std::endl;
Array<int, 200> arr2;
std::cout << "arr2.getSize(): " << arr2.getSize() << std::endl;
}
Bei genauer Betrachtung des Klassen-Templates Array fÀllt auf, dass die Methode getSize lediglich vom Typparameter N abhÀngt. Daher werde ich den Code refaktorieren und eine Klasse ArrayBase definieren, die nur vom Typparameter T abhÀngt:
// genericArrayInheritance.cpp
#include <cstddef>
#include <iostream>
template<typename T>
class ArrayBase {
protected:
ArrayBase(std::size_t n): size(n) {}
std::size_t getSize() const {
return size;
};
private:
std::size_t size;
};
template<typename T, std::size_t n>
class Array: private ArrayBase<T>{
public:
Array(): ArrayBase<T>(n){}
std::size_t getSize() const {
return ArrayBase<T>::getSize();
}
private:
T data[n];
};
int main(){
Array<int, 100> arr1;
std::cout << "arr1.getSize(): " << arr1.getSize() << std::endl;
Array<int, 200> arr2;
std::cout << "arr2.getSize(): " << arr2.getSize() << std::endl;
}
Array besitzt zwei Template-Parameter fĂŒr den Datentyp T und die LĂ€nge n. Hingegen hat ArrayBase nur ein Template-Parameter fĂŒr den Datentyp T. Arrray ist von ArrayBase abgeleitet. Das heiĂt, dass ArrayBase zwischen allen Instanzen von Array geteilt wird, die denselben Datentyp T verwenden. In dem konkreten Fall bedeutet dies, dass die getSize-Methode von Array die von ArrayBase verwendet.
Danke CppInsight [8] kann ich den vom Compiler erzeugten Code direkt zeigen.
Hier ist die Instanziierung von ArrayBase<int>:
Und hier sind die Instanziierungen fĂŒr Array<int, 100>:
und Array<int, 200>:
Wie geht's weiter?
NatĂŒrlich gibt es mehr Regel zu Template-Definitionen. Daher geht meine Geschichte zu Templates im nĂ€chsten Artikel weiter. Ich hoffe, dass meine ErlĂ€uterungen zu Templates ausreichend sind, denn ich weiĂ, dass viele Programmierer Templates nicht verwenden wollen. FĂŒr mich sind die zentralen Ideen von Templates einfach zu verstehen, aber ihre Syntax besitzt noch einiges an Verbesserungspotenzial. ( [9])
URL dieses Artikels:
https://www.heise.de/-4245897
Links in diesem Artikel:
[1] http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rt-depend
[2] http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rt-scary
[3] http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rt-nondependent
[4] http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rt-depend
[5] http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rt-scary
[6] http://www.open-std.org/jtc1/sc22/WG21/docs/papers/2009/n2911.pdf
[7] http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rt-nondependent
[8] https://cppinsights.io/
[9] mailto:rainer@grimm-jaud.de
Copyright © 2018 Heise Medien