Forum: PC-Programmierung Methoden geerbter Klassen mit unterschiedlichen Rückgabewerten


von olli23 (Gast)


Lesenswert?

Hallo,

ich habe eine abstrakte Parameter Klasse.
1
class Param
2
{
3
public:
4
    Param();
5
    ~Param();
6
7
  <pseudoCode>  anyType getValue() = 0; </pseudoCode>
8
  <pseudoCode> void setValue(anyType value) = 0; </pseudoCode>
9
};

und davon geerbt verschiedene sorten von Params z.B. Integer
1
class ParamInt : public Param
2
{
3
public:
4
    ParamInt ();
5
    ~ParamInt ();
6
7
    int getValue() = 0; 
8
    void setValue(int value) = 0;
9
private: 
10
    int value;
11
};

oder Double
1
class ParamDouble : public Param
2
{
3
public:
4
    ParamDouble ();
5
    ~ParamDouble ();
6
7
    double getValue() = 0; 
8
    void setValue(double value) = 0;
9
private: 
10
    double value;
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?

von Mark B. (markbrandis)


Lesenswert?

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?

von Rolf M. (rmagnus)


Lesenswert?

Templates

von Noch einer (Gast)


Lesenswert?

>Templates

Der Mann hat gefragt: "Gibt es hierfür eine elegante Lösung?" :-)

von Carl D. (jcw2)


Lesenswert?

Noch einer schrieb:
>>Templates
>
> Der Mann hat gefragt: "Gibt es hierfür eine elegante Lösung?" :-)

Nur zu! Wie macht man das elegant ohne Templates?

von Wilhelm M. (wimalopaan)


Lesenswert?

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 ...

von Dr. Sommer (Gast)


Lesenswert?

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).

von Oliver S. (oliverso)


Lesenswert?

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

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

von Karl Käfer (Gast)


Lesenswert?

Noch einer schrieb:
>>Templates
>
> Der Mann hat gefragt: "Gibt es hierfür eine elegante Lösung?" :-)

Richtig -- und Templates sind die elegante Lösung.

von Mikro 7. (mikro77)


Lesenswert?

Dann kannst du sicher auch deine Template-Lösung hier zeigen, so dass 
man  sehen kann wie "elegant" das ist.

von Karl Käfer (Gast)


Lesenswert?

olli23 schrieb:
1
template<typename T>
2
class Param {
3
public:
4
    // ctor, dtor
5
    T getValue() = 0;
6
    void setValue(T value) = 0;
7
private:
8
    T value;
9
> };

von Mark B. (markbrandis)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

streich das "=0"

von Mikro 7. (mikro77)


Lesenswert?

Karl Käfer schrieb:
> olli23 schrieb:
>
>
1
> template<typename T>
2
> class Param {
3
> public:
4
>     // ctor, dtor
5
>     T getValue() = 0;
6
>     void setValue(T value) = 0;
7
> private:
8
>     T value;
9
>> };
10
>

Nö.

Das Beispiel des TS nutzt eine gemeinsame virtuelle Basisklasse als 
Interface. Darauf sollen dann die Ableitungen basieren.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Mikro 7. schrieb:
> Karl Käfer schrieb:
>> olli23 schrieb:
>>
>>
1
>> template<typename T>
2
>> class Param {
3
>> public:
4
>>     // ctor, dtor
5
>>     T getValue() = 0;
6
>>     void setValue(T value) = 0;
7
>> private:
8
>>     T value;
9
>>> };
10
>>
>
> 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!

von Mikro 7. (mikro77)


Lesenswert?

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.

von xxx (Gast)


Lesenswert?

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?:
1
class ParamInt : public Param<int>
2
{
3
public:
4
};
5
6
class ParamDouble : public Param<double>
7
{
8
public:
9
};
10
11
usw....

von xxx (Gast)


Lesenswert?

Hm, da fehlen natürlich noch die virtuellen Funktionen (die aber dann 
besser auch im template aufgehoben wären).

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

Beitrag #4995875 wurde vom Autor gelöscht.
von Wilhelm M. (wimalopaan)


Lesenswert?

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!

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Mikro 7. (mikro77)


Lesenswert?

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. ;-)

von Wilhelm M. (wimalopaan)


Lesenswert?

Na so:
1
#include <iostream>
2
3
template<typename ValueType>
4
class Parameter {
5
public:
6
    Parameter(const ValueType& value) : mValue(value) {}
7
    ValueType getValue() const {return mValue;}
8
private:
9
    const ValueType mValue{};
10
};
11
12
int main() {
13
    Parameter p1(1);
14
    Parameter p2(2.2);
15
    
16
    auto v1 = p1.getValue();
17
    auto v2 = p2.getValue();
18
    
19
    std::cout << v1 << '\n';
20
    std::cout << v2 << '\n';
21
}

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

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!!!

von Mikro 7. (mikro77)


Lesenswert?

Das ist das gleiche wie oben. Die gemeinsame Basisklasse fehlt.

Edit: Problemstellung steht da.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Mikro 7. schrieb:
> Das ist das gleiche wie oben. Die gemeinsame Basisklasse fehlt.

Wozu sollte ich die haben???

von Wilhelm M. (wimalopaan)


Lesenswert?

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!

von olli23 (Gast)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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?

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

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 ...

von Dr. Sommer (Gast)


Lesenswert?

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:
1
#include <memory>
2
#include <vector>
3
#include <iostream>
4
#include <utility>
5
6
class Param {
7
  public:
8
    virtual void print () = 0;
9
};
10
class ParamInt : public Param {
11
  private:
12
    int m_i;
13
  public:
14
    ParamInt (int i = 0) : m_i (i) {}
15
    
16
    void setValue (int i) { m_i = i; }
17
    virtual void print () override {
18
      std::cout << m_i;
19
    }
20
};
21
22
template <typename T>
23
class ParamGeneric : public Param {
24
  private:
25
    T m_val;
26
  public:
27
    ParamGeneric (const T& val) : m_val (val) {}
28
    ParamGeneric (T&& val) : m_val (std::move (val)) {}
29
    
30
    void setValue (const T& val) { m_val = val; }
31
    void setValue (T&& val) { m_val = std::move (val); }
32
33
    virtual void print () override {
34
      std::cout << m_val;
35
    }
36
};
37
38
int main () {
39
  std::vector<std::unique_ptr<Param>> params;
40
  { // Variante 1, mit setValue
41
    auto newParam = std::make_unique<ParamInt> ();
42
    newParam->setValue (std::stoi ("42"));
43
    params.push_back (std::move (newParam));
44
  }
45
  { // Variante 2, mit Konstruktor
46
    auto newParam = std::make_unique<ParamInt> (std::stoi ("21"));
47
    params.push_back (std::move (newParam));
48
  }
49
  { // Variante 3, mit template
50
    auto newParam = std::make_unique<ParamGeneric<double>> (std::stod ("10.5"));
51
    params.push_back (std::move (newParam));
52
  }
53
  
54
  for (auto& p : params) {
55
    p->print ();
56
    std::cout << std::endl;
57
  }
58
}
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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.
1
#include <iostream>
2
#include <string>
3
#include <vector>
4
5
class Parameter final {
6
public:
7
    Parameter(const std::string& raw) : mData(raw) {}
8
  
9
    template<typename T>
10
    T valueAs() const;
11
private:
12
    const std::string mData;    
13
};
14
15
template<>
16
int Parameter::valueAs<int>() const {
17
    return std::stoi(mData);
18
} 
19
template<>
20
double Parameter::valueAs<double>() const {
21
    return std::stod(mData);
22
} 
23
24
int main() {
25
    std::vector<Parameter> parameters;
26
27
    parameters.push_back({"1"});    
28
    parameters.push_back({"2.2"});    
29
    
30
    for(const auto& p : parameters) {
31
        std::cout << p.valueAs<int>() << '\n';
32
        std::cout << p.valueAs<double>() << '\n';
33
    }
34
    
35
}

von Mikro 7. (mikro77)


Lesenswert?

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).

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Wilhelm M. (wimalopaan)


Lesenswert?

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.

: Bearbeitet durch User
von Peter (Gast)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.