und davon geerbt verschiedene sorten von Params z.B. Integer
1
classParamInt:publicParam
2
{
3
public:
4
ParamInt();
5
~ParamInt();
6
7
intgetValue()=0;
8
voidsetValue(intvalue)=0;
9
private:
10
intvalue;
11
};
oder Double
1
classParamDouble:publicParam
2
{
3
public:
4
ParamDouble();
5
~ParamDouble();
6
7
doublegetValue()=0;
8
voidsetValue(doublevalue)=0;
9
private:
10
doublevalue;
11
};
Für die setValue() methode könnte ich jetzt in der abstrakten
Param-Klasse einfach alle typen virtuell deklarieren und überladen und
dann jeweils in den geerbten Klassen die benötigte Methode
implementieren.
setParam(int)
setParam(double)
setParam(std::string)
usw.
das ist etwas unschön weil dann immer alle Methoden in allen Klassen da
sind (die nur nichts tun) aber funktioniert zumindes.
aber für die getParm() Methode geht das ja nicht da die Methoden sich
nur durch den Rückgabewert unterscheiden. Gibt es hierfür eine elegante
Lösung?
olli23 schrieb:> das ist etwas unschön weil dann immer alle Methoden in allen Klassen da> sind (die nur nichts tun) aber funktioniert zumindes.>> aber für die getParm() Methode geht das ja nicht da die Methoden sich> nur durch den Rückgabewert unterscheiden. Gibt es hierfür eine elegante> Lösung?
Das könnte ein Hinweis darauf sein, dass die entsprechenden Methoden in
der Basisklasse vielleicht fehl am Platz sind.
Ich würde den Ansatz an sich nochmal überdenken. Was genau ist der Sinn
und Zweck dieser Klasse?
Noch einer schrieb:>>Templates>> Der Mann hat gefragt: "Gibt es hierfür eine elegante Lösung?" :-)
Das ist gleichzeitig die richtige und elegante Lösung.
Denn der DT "Parameter" soll offensichtlich mit einem anderen DT
parametriert werden. Das nennt sich parameterische Polymorphie und dei
realisiert man in C++ mit templates.
Eine Inklusions-Polymorphie ist hier nicht gegeben ...
olli23 schrieb:> Für die setValue() methode könnte ich jetzt in der abstrakten> Param-Klasse einfach alle typen virtuell deklarieren und überladen und> dann jeweils in den geerbten Klassen die benötigte Methode> implementieren.
Und welchen Sinn soll das haben? Wenn du irgendwo eine Referenz auf die
Basis Klasse hast, woher weißt du dann welche setValue du jetzt aufrufen
musst? Wenn du weißt, dass eine bestimmte Param Instanz eigentlich ein
ParamInt ist, wo du also die int Variante aufrufen musst, kannst du die
auch einfach nach ParamInt casten und die Funktion aufrufen, und die
Funktionen somit alle nicht-virtuell machen (also nicht in Param
deklarieren).
Für unelegante Probleme gibt es halt keine eleganten Lösungen.
Denn was auch immer man sich da mit Templates zusammenbasteln könnte, am
Ende muß man doch beim Aufruf der Funktion wissen, was da für ein
Datentyp zurückgegeben wird. Das erschwert oder verhindert dann im
Endeffekt die Nutzung virtueller Funktionen, egal, ob nun runtime oder
compiletime.
Zwar gibt es auch dafür Lösungen außerhalb der Sprachdefinition, wie
boost.any oder QVariant in Qt, aber wer sowas benutzt, stellt die
Ausgangsfrage erst gar nicht.
Wenn die Menge der möglichen Rückkgabetypen allerdings nur numerische
Datentypen umfasst, könnte man auch einfach immer double zurückgeben.
Oliver
Oliver S. schrieb:> Für unelegante Probleme gibt es halt keine eleganten Lösungen.
Wir wissen ja noch gar nicht, was er wirklich (!) für ein Problem hat.
Für das, was er da gepostet hat, kommt jedenfalls parametrische
Polymorphie in Frage.
Wilhelm M. schrieb:> Oliver S. schrieb:>> Für unelegante Probleme gibt es halt keine eleganten Lösungen.>> Wir wissen ja noch gar nicht, was er wirklich (!) für ein Problem hat.
Das ist genau der Punkt. Wir kennen nur einen Lösungsansatz, der
vielleicht passt oder vielleicht auch nicht. Deswegen ja auch meine
Frage, was denn eigentlich der Sinn und Zweck dieser Klasse sein soll.
>> Nö.>> Das Beispiel des TO nutzt eine gemeinsamen virtuellen Basisklasse als> Interface. Darauf sollen dann die Ableitungen basieren.
Auch Nö.
Er noch keinen use-case gezeigt!
Der TS hat eine Basisklasse (Interface) und zwei abgeleitete Klassen
vorgestellt. Er fragt nach virtuellen get() Methoden, die sich lediglich
im Rückgabewert unterscheiden. Das ist der "Use-Case".
Möglicherweise hat er etwas viel einfacheres im Sinn. Möglicherweise
soll es wirklich nur ein Container ohne Basisklasse sein, den man durch
o.g. Template abbilden kann. Möglicherweise nicht. Das ist Spekulation.
Wilhelm M. schrieb:>>> template<typename T>>>> class Param {>>> public:>>> // ctor, dtor>>> T getValue() = 0;>>> void setValue(T value) = 0;>>> private:>>> T value;>>>> };>>>
Fehlt da dann nicht noch die Ableitung/Ableitungen?:
Mikro 7. schrieb:> Der TS hat eine Basisklasse (Interface) und zwei abgeleitete Klassen> vorgestellt. Er fragt nach virtuellen get() Methoden, die sich lediglich> im Rückgabewert unterscheiden. Das ist der "Use-Case".>> Möglicherweise hat er etwas viel einfacheres im Sinn. Möglicherweise> soll es wirklich nur ein Container ohne Basisklasse sein, den man durch> o.g. Template abbilden kann. Möglicherweise nicht. Das ist Spekulation.
Was er dort vorschlägt ist aber nicht sinnvoll, genauer Quatsch. Weil er
in ParamInt nicht nur setValue(int), sondern auch setValue(double)
überschreiben müsste.
M.E. hat er es nur so angedacht, weil er keine andere / bessere
Möglichkeit weiß. Aber wie gesagt: alles Spekulation.
xxx schrieb:> Hm, da fehlen natürlich noch die virtuellen Funktionen (die aber dann> besser auch im template aufgehoben wären).
Und wäre es genauso wenig sinnvoll!
Möglicherweise schwebt dem TE vor, am Ende alle Parameterobjekte
unabhängig vom Parametertyp in einer Liste (oder einem anderen
Container) zusammenzufassen. Das geht mit der Verwendung von
Template-Klassen natürlich nicht, weil die Template-Instanzen
unterschiedlichen Typs sind und keine gemeinsame Basisklasse haben. Aber
selbst wenn so etwas möglich wäre¹, frage ich mich, wo der Nutzen läge.
Wäre es nicht sinnvoller, mehrere solcher Parameterlisten (eine jeden
Parametertyp) anzulegen? Dann funktioniert es auch problemlos mit den
Templates. Man kann dann bspw. in einer Schleife über alle Parameter
gleichen Typs iterieren, was durchaus ganz praktisch ein kann.
Aber vielleicht meldet sich der TE ja noch einmal und erählt etwas mehr
von seinem Vorhaben.
—————————————
¹) Wenn man die Parameterklassen in eine union, eine boost::variant oder
ein boost::any einpackt, lässt sich das indirekt sogar realisieren.
Dass der Ansatz im Posting des TS (mit den versch. Typen) grundsätzlich
geprüft werden sollte, wurde bereits in der ersten Antwort geschrieben
(die sofort negativ bewertet wurde). Darauf kam nie eine Antwort vom TS.
Bleibt die Fragestellung wie sie ist (die gar nicht mal so ungewöhnlich
ist). Ob das ein sinnvolles Szenario ist oder nicht kann jeder für sich
selbst entscheiden. Imho gibt es durchaus Anwendungsfälle.
Darauf war nun Templates eine richtige Antwort. Mit Templates geht der
Template Wert in die Signatur ein. Die Alternative wäre ein Dummy Arg
für die Signatur (einfacher zu schreiben).
Für "beliebige" Argumente hätte ich eigentlich auch den boost::any
Ansatz vorgeschlagen (Oliver S.); Alternativ boost::variant (yalu).
Da aber mehrfach Template! kam, wäre es ja schön, wenn jemand die Lösung
für das ursprüngliche Problem gezeigt hätte (was halt gar nicht so
"elegant" ist). Man kann sich natürlich auch eine neue Problemstellung
suchen, die nicht gefragt wurde, und darauf eine Antwort posten; was
hier im Forum nicht so selten vorkommt. ;-)
Mikro 7. schrieb:>> Da aber mehrfach Template! kam, wäre es ja schön, wenn jemand die Lösung> für das ursprüngliche Problem gezeigt hätte (was halt gar nicht so> "elegant" ist). Man kann sich natürlich auch eine neue Problemstellung> suchen, die nicht gefragt wurde, und darauf eine Antwort posten; was> hier im Forum nicht so selten vorkommt. ;-)
Die Problemstellung kennt ja keiner!!!
Mikro 7. schrieb:> Das ist das gleiche wie oben. Die gemeinsame Basisklasse fehlt.>> Edit: Problemstellung steht da.
Nein, da steht eine unglückliche Lösung für ein Problem, was keiner
kennt!
Danke erstmal für die vielen Antworten.
Zu den dort aufgekommen Fragen:
Der Zweck ist unterschiedliche Parameter-Typen in einem std::vector
zusammenzufassen. Die Werte für die Parameter werden dabei aus einer
Textdatei gelesen und mit setVal() gesetzt (mit einer Schleife über alle
Parameter, wobei je nachdem was für ein Typ es ist noch ein atof oder
atoi angewandt wird.
Wenn ich jetzt setVal in der Basisklasse überlade habe ich eine (wie ich
finde) recht elegante Lösung, da durch die Überladung automatisch immer
die richtige Methode gewählt wird und ich keinen type_cast machen muss
um die Methode aufzurufen. Die unnötigen virtuellen funktionen werden
jeweis mit leerem Rumpf deklariet und tun einfach nichts.
Was eben schön wäre wenn das genauso für getVal gehen würde.
Kontainer wie QVariant oder ähnliches will ich nicht verweden, da das
ganze auch ohne Qt / Boost... kompilieren soll.
olli23 schrieb:> Was eben schön wäre wenn das genauso für getVal gehen würde.>> Kontainer wie QVariant oder ähnliches will ich nicht verweden, da das> ganze auch ohne Qt / Boost... kompilieren soll.
Woher weißt Du denn, ob es jetzt ein int, double oder sonstwas als
unterliegender Typ ist. Also welcher der Beobachter-Funktionen (getter)
Du nehmen musst bzw. wolltest?
olli23 schrieb:>> Wenn ich jetzt setVal in der Basisklasse überlade habe ich eine (wie ich> finde) recht elegante Lösung, da durch die Überladung automatisch immer> die richtige Methode gewählt wird und ich keinen type_cast machen muss> um die Methode aufzurufen. Die unnötigen virtuellen funktionen werden> jeweis mit leerem Rumpf deklariet und tun einfach nichts.
Oh je: das fällt ja durch jeden Konformanztest ...
olli23 schrieb:> Die Werte für die Parameter werden dabei aus einer> Textdatei gelesen und mit setVal() gesetzt (mit einer Schleife über alle> Parameter, wobei je nachdem was für ein Typ es ist noch ein atof oder> atoi angewandt wird.>> Wenn ich jetzt setVal in der Basisklasse überlade habe ich eine (wie ich> finde) recht elegante Lösung, da durch die Überladung automatisch immer> die richtige Methode gewählt wird
Das passt nicht zusammen. Wenn du einen "int" aus der Datei ausliest,
musst du "new ParamInt" aufrufen, um die richtige Instanz anzulegen. Und
dann könntest du auch direkt eine ganz normale, nicht virtuelle,
Funktion setValue(int) aufrufen, und danach die Instanz in den vector
packen. Du könntest auch den int direkt an den Konstruktor übergeben.
Dann gibt es wie von anderen bereits erwähnt noch die Möglichkeit, die
konkrete Klasse als template auszuführen, um Tipperei zu sparen. Hier
sind alle Varianten dargestellt:
Kein Cast und keine virtuelle Funktion zum Setzen und keine leeren
Funktionen nötig... Die Funktion "print" ist als Beispiel gedacht für
"normale" generische Funktionen.
Auch noch eine Variante. Kommt ganz ohne Vererbung aus und verschiebt
das Parsen auf später. Als immutable-DT ...
Geht die Wandlung nicht -> exception. Falls man das nicht möchte, wäre
ein
std::optional<> als return-DT machbar.
olli23 schrieb:> Kontainer wie QVariant oder ähnliches will ich nicht verweden, da das> ganze auch ohne Qt / Boost... kompilieren soll.
Eine "Discriminated Union" für einen Container kann man auch einfachst
selbst schreiben. Bspw.
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.18.3325&rep=rep1&type=pdf
Es reicht schon das Bsp. auf der ersten Seite (Enum als Discriminator +
Union mit deine drei Datentypen).
olli23 schrieb:> Der Zweck ist unterschiedliche Parameter-Typen in einem std::vector> zusammenzufassen. Die Werte für die Parameter werden dabei aus einer> Textdatei gelesen und mit setVal() gesetzt (mit einer Schleife über alle> Parameter, wobei je nachdem was für ein Typ es ist noch ein atof oder> atoi angewandt wird.
Das habe ich verstanden.
> Was eben schön wäre wenn das genauso für getVal gehen würde.
Das habe ich nicht verstanden.
Welchen Nutzen bringt es, ein universelles getVal zu haben? Hast du
vielleicht ein Beispiel dafür?
Prinzipiell steht doch an der Stelle, wo getVal aufgerufen wird,
praktisch immer auch der Datentyp fest, so dass man genauso gut auch
eine auf diesen Datentyp spezialisierte Methode (also bspw. getValInt,
getValDouble usw.) direkt aufrufen kann.
Yalu X. schrieb:> Prinzipiell steht doch an der Stelle, wo getVal aufgerufen wird,> praktisch immer auch der Datentyp fest, so dass man genauso gut auch> eine auf diesen Datentyp spezialisierte Methode (also bspw. getValInt,> getValDouble usw.) direkt aufrufen kann.
Sehe ich genauso: Die Klasse Parameter kann als domänenspezifischer-DT
als
Wrapper um einen std::string dienen (s.o.) und gut ist (wobei man obigen
absichtlichen linker-Fehler für nicht unterstütze Wandlungen noch per
type-trait in einen Compiler-Fehler umwandeln könnte).
Man kann aber auch die Template-Elementfunktion ganz gegen konkrete
Beobachter austauschen.
Yalu X. schrieb:> Welchen Nutzen bringt es, ein universelles getVal zu haben? Hast du> vielleicht ein Beispiel dafür?>> Prinzipiell steht doch an der Stelle, wo getVal aufgerufen wird,> praktisch immer auch der Datentyp fest, so dass man genauso gut auch> eine auf diesen Datentyp spezialisierte Methode (also bspw. getValInt,> getValDouble usw.) direkt aufrufen kann.
Wenn der Datentyp feststeht, kann man das Objekt auch gleich auf diesen
Typ casten und eine nicht-virtuelle getVal-Methode aufrufen. Wenn man
den Typ nicht kennt, findet man ihn per dynamic_cast raus.