Hallo,
ich habe eine Klasse Handler, welche für verschiedene, in einer
Enumeration definierte Werte unterschiedlich implementiert wird.
Allerdings ist die Implementierung für verschiedene Wertebereiche
gleich. Also möchte ich ungefähr so etwas realisieren:
1
template<uint8nr>
2
classHandler{};
3
4
template<>
5
classHandler<0..3>{
6
domeSomething(){
7
// Algorithmus 1
8
// irgendwas machen wobei nr wichtig ist
9
}
10
11
12
template<>
13
classHandler<4..7>{
14
domeSomething(){
15
// Algorithmus 2
16
// irgendwas ganz anderes machen wobei nr wichtig ist
17
}
18
19
// noch 2,3 weitere Handler
Ist so etwas möglich?
Ein Strategiemuster möchte ich nicht implementieren, weil die
nr-abhängigen Teile alle Methoden und auch den Konstruktor betreffen und
ich in eingebetteten Systemen arbeite (AVR mit GCC).
Danke
Meines Wissens nach geht das nicht, da Templates nur auf Grundlage des
Datentypes unterschiedlich instantiiert werden. Die Elemente eines enums
stellen aber keine unterschiedlichen Datentypen dar. Afaik kann man also
eine template Funktion z.B. für einen int spezialisieren und so von
ihrer "allgemeinen Implementierung" unterscheiden.
Der Code funktioniert so, dass das Template power aufgerufen wird.
Dieses wertet nun aus, ob der Exponent negativ ist und rechnet in dem
Fall . Zum Berechnen der eigentlichen Potenz wird die Struktur
pow_helper benutzt. Diese ruft sich selbst auf („Rekursion“), wobei der
Exponent bei jedem Aufruf um 1 reduziert wird. pow_helper besitzt eine
so genannte Spezialisierung für den Fall, dass der Exponent 0 ist und
liefert in dem Fall das Ergebnis 1 zurück.
[/quote]
stammt aus Wikipedia
Und ich kann mich auch erinnern, dass die Template Gurus ganze
'Taschenrechner' mit Templates gebaut haben, die Expressions zur
Compiletime auswerten
Aber ich gestehe: Ich bin bei Templates zu schwach.
Und ob 0..3 syntaktisch korrekt sein kann, darüber will ich gar nicht
näher nachdenken (ich denke nicht, dass das ok ist. Zumindest habe ich
sowas noch nie gesehen)
Also ich würde da vermutlich gar nicht spezialisieren,
sondern das mit if oder switch brutal in den Quelltext
schreiben:
1
template<uint8_tnr>
2
classHandler
3
{
4
voiddomeSomething()
5
{
6
switch(nr)
7
{
8
case0:
9
case1:
10
case2:
11
case3:
12
// Algorithmus 1
13
// irgendwas machen wobei nr wichtig ist
14
break;
15
16
case4:
17
case5:
18
case6:
19
case7:
20
// Algorithmus 2
21
// irgendwas machen wobei nr wichtig ist
22
break;
23
24
// case....
25
26
default:
27
28
break;
29
}
30
}
31
};
Wenn jetzt jemand einwendet, das wäre ineffizient, dann
verweise ich flink auf den Optimierer.
Schließlich ist nr ja konstant, also wird hoffentlich der
jeweils überflüssige Plunder eh rausgewirfen.
Klaus Wachtler schrieb:
> Also ich würde da vermutlich gar nicht spezialisieren,> sondern das mit if oder switch brutal in den Quelltext> schreiben:
Ist ja langweilig ;-)
> Wenn jetzt jemand einwendet, das wäre ineffizient, dann> verweise ich flink auf den Optimierer.> Schließlich ist nr ja konstant, also wird hoffentlich der> jeweils überflüssige Plunder eh rausgewirfen.
Ich gehe davon aus, daß das so optimiert wird.
Übrigens: Es gibt einen Unterschied zwischen unseren Versionen. Bei
meiner bricht der Compiler mit einer Fehlermeldung ab, wenn man einen
Wert größer als 7 angibt, bei deiner wird das stillschweigend ignoriert.
Das ist richtig.
Aber wenn ich es richtig verstanden habe, will er eigentlich
gar keine Zahlen nehmen, sondern sich eine enum bauen und
deren Werte dann als Templateargument nehmen.
Dann tritt der Fall ja gar nicht auf:
Hallo,
aha, ich verstehe. Wenn ich einen solchen Vergleich in die Deklaration
hineinbringe, meckert der Compiler "template argument ... involves
template parameter(s)..." Das heißt, die Templateparameter können nicht
als Templateargument ausgewertet werden. Was aber ginge, wäre folgendes:
1
template<uint8nr,uint8type>
2
classHandler{};
3
4
// für nr == 0..3
5
template<uint8nr>
6
classHandler<1>{
7
voiddomeSomething(){
8
// Algorithmus 1
9
// irgendwas machen wobei nr wichtig ist
10
}
11
12
// für nr == 4..7
13
template<uint8nr>
14
classHandler<2>{
15
voiddomeSomething(){
16
// Algorithmus 2
17
// irgendwas ganz anderes machen wobei nr wichtig ist
18
}
19
20
// usw...
Jetzt könnte man das ganze über einen Delegator ansprechen.
1
template<uint8nr>
2
classHandlerSwitcher{
3
public:
4
Handler<nr,(nr/4)>*operator->(){
5
return&handler;
6
}
7
private:
8
Handler<nr,(nr/4)>handler;
9
};
Die Benutzung erfolgt dann ohne dass man weiß, welcher Handler denn nun
im Hintergrund verwendet wird:
1
HandlerSwitcher<0>handlerMitAlgorithmus1;
2
HandlerSwitcher<4>handlerMitAlgorithmus2;
3
4
handlerMitAlgorithmus1->doSomething();
5
//...
Das hat aber den Nachteil, dass jedesmal ein Pointer auf den Handler
gespeichert werden muss.
Eine andere Möglichkeit wäre, es über Vererbung zu lösen. Bei der Angabe
der Elternklasse sind Vergleiche und ähnliche Dinge nämlich erlaubt:
Ich habe beide Möglichkeiten getestet. Beide führen lustigerweise zur
fast gleichen Codegröße, jedoch ist der RAM-Verbrauch bei letzerer
Variante ein klein bisschen größer. Möglicherweise liegt das daran, dass
die Handlerklassen noch 2 virtuelle Methoden beinhalten. Ich weiß aber
nicht genau, was dabei im Hintergrund mit den vtables geschieht schäm.
Allerdings verlieren beide Möglichkeiten eindeutig gegen die Variante,
für jeden Algorithmus eine eigene Klasse zu schreiben und den Parameter
nr als Membervariable zu speichern und als Parameter im Konstruktor zu
übergeben, etwa so:
1
classHandler1{
2
public:
3
Handler1(uint8nr){
4
//...
5
};
6
7
voiddoSomething(){
8
//...
9
}
10
};
11
12
// und so weiter
Der Code wird kleiner, der RAM-Verbrauch ist auch geringer. Das liegt in
meinem Fall wohl daran, dass die Codeähnlichkeiten zwischen den
einzelnen Klassen wohl doch zu groß sind.
Letztendlich habe ich noch einmal eine Kombination aus der herkömmlichen
Lösung (für jeden Algorithmus eine extra Klasse, den Parameter nr durch
den Konstruktor übergeben) und das Delegationsmuster ausprobiert. Dabei
wird nr über den Konstruktor übergeben und nur der Algorithmus über den
Templateparameter ausgewählt. Und siehe da: Code- und Ramverbrauch
entsprechen in etwa der herkömmlichen Lösung, nur dass ich jetzt von
außen nicht mehr immer die explizite Klasse mit ihrem Algorithmus
angeben muss.
1
template<uint8type>
2
classHandler{};
3
4
template<>
5
classHandler<1>{
6
public:
7
Handler(uint8nr){
8
//...
9
};
10
11
voiddoSomething(){
12
//...
13
}
14
};
15
16
//... und noch weitere Handler
17
18
19
template<uint8nr>
20
classHandlerSwitcher{
21
public:
22
Handler<(nr/4)>*operator->(){
23
return&handler;
24
}
25
private:
26
Handler<(nr/4)>handler;
27
};
Die Bentuzung erfolgt wie schon beschrieben. Also ist die "Smart
Pointer"-Lösung (bzw. das Delegationsmuster) gar nicht so übel. So, alle
noch da? War ein netter Exkurs.
Gruß
rw
Oh, während ich an meinem Text gebastelt habe, gab's weitere Antworten.
Danke Danke.
@Klaus Wachtler: Das der Optimizer sehr gut optimiert, ist mir auch
aufgefallen. Deine Lösung probiere ich in meinem Fall noch mal aus.
klaus schrieb:
> Die Spezialisierung mit einer Konstanten scheint er irgendwie nicht zu> mögen, oder habe ich etwas übersehen ?
Du versuchst, einen Typ-Template-Parameter mit einem Wert zu
spezialisieren. Das geht natürlich nicht.
rw schrieb:
> Ich habe beide Möglichkeiten getestet. Beide führen lustigerweise zur> fast gleichen Codegröße, jedoch ist der RAM-Verbrauch bei letzerer> Variante ein klein bisschen größer. Möglicherweise liegt das daran,> dass die Handlerklassen noch 2 virtuelle Methoden beinhalten. Ich weiß> aber nicht genau, was dabei im Hintergrund mit den vtables geschieht> schäm.
Wenn du avr-g++ verwendest, hast du da das Problem, daß er, weil GCC
eigentlich nicht für Harvard-Architekturen gedacht ist, alle vtables im
RAM hält. Das bedeutet, daß du pro Klasse und virtueller Memberfunktion
Platz für einen Zeiger im RAM verbrauchst (und im Flash natürlich auch
nochmal, für die Initialisierungswerte).
Übrigens habe ich es so implementiert wie von Klaus Wachtler
vorgeschlagen.
Der gcc optimiert das switch/case weg und so ist der Code auch am
leichtesten zu lesen/verstehen. Und genau das ist nicht zu verachten. :)
Dafür hast du aber hoffentlich nicht ein knappes Jahr gebraucht, oder?
:-)
Oder wolltest du die Lesbarkeit und Verständlichkeit nach einiger
Zeit testen?
Im Ernst: Das ist übrigens ein Test, der gemeinhin vernachlässigt
wird - m.E. zu Unrecht.
Aber sicher ;-)
Nö, ich bin grad mal bei der Suche nach was anderem über diesen Thread
gestolpert und da dachte ich, schreib ich doch mal was dazu.
Geworden ist es damals übrigens eine generische Treiberklasse für die
INTx-Eingänge des ATMega2560. Der hat ja 8 an der Zahl, wobei die
Register immer bei 4 Stück gleich sind. Ich wollte mit minimalem
Codeaufwand ein maximal effizientes Ergebnis in C++ haben.
Die Lektüre dieses Buches hat übrigens Templates für mich wirksam
entzaubert:
http://www.amazon.de/Templates-Complete-Nicolai-M-Josuttis/dp/0201734842/
Ja, es ist gar nicht so einfach, lesbaren Code zu schreiben. Und Du hast
recht: Wenn ich mir das Zeug von vor einem Jahr anschaue... ohje. Leider
wird diese Disziplin an den Hochschulen nicht gelehrt, genauso wenig wie
Codelesen. Kommt also nur durch Erfahrung.