Forum: PC-Programmierung C++: richtig erben? :(


von Rasputin (Gast)


Angehängte Dateien:

Lesenswert?

Hallo liebe Leute,

Ich bin im Moment daran, meine C++ Kenntnisse etwas zu vertiefen und 
erweitern. Nun bin ich auf ein Problem gestossen, an dem ich nun schon 
seid ein paar Tagen 'rumkaue, und das mich einfach nicht loslässt. :((

Nun denn... ich habe eine Klasse (fixedpoint.h, siehe Anhang), und von 
der möchte ich erben (nach tester, siehe unten). Am liebsten hätte ich 
eine komplette Kopie (inkl. Constructors, etc), zu der ich weitere 
Funktionalität hinzufügen kann. Jedoch ohne X "Proxy-Methoden" schreiben 
zu müssen. Das muss doch möglich sein, oder nicht?
Hier mein Testcode:
1
typedef unsigned int uint;
2
3
#include "fixedpoint.h"
4
#include <stdlib.h>
5
6
class tester : public fixedpoint<100> {
7
protected:
8
  typedef tester self;
9
  typedef fixedpoint parent;
10
11
public:
12
  using fixedpoint::fixedpoint;
13
  using fixedpoint::operator=;
14
};
15
16
17
int main() {
18
  tester t(5);
19
  t = 50.4949;
20
  tester q = 1;
21
  tester = 0.01;
22
  q = t + 1.2;
23
  q += 1;
24
  q.print();
25
26
  while (1)
27
   _sleep(1);
28
}

Wenn ich die Klasse so schreibe, würde es funktionieren, aber das kann 
doch nicht die Lösung sein. Oder doch?
1
class tester : public fixedpoint<100> {
2
protected:
3
  typedef tester self;
4
  typedef fixedpoint parent;
5
6
public:
7
  inline tester() : fixedpoint() { };
8
  inline tester(const int& v) : parent(v) { };
9
  inline tester(const uint& v) : parent(v) { };
10
  inline tester(const float& v) : parent(v) { };
11
  inline tester(const double& v) : parent(v) { };
12
13
  inline tester(const parent& src) : parent(src) { };
14
15
  inline self& operator=(const int& rhs) {
16
    parent::operator=(rhs);
17
    return *this;
18
  }
19
20
  inline self& operator=(const uint& rhs) {
21
    parent::operator=(rhs);
22
    return *this;
23
  }
24
25
  inline self& operator=(const float& rhs) {
26
    parent::operator=(rhs);
27
    return *this;
28
  }
29
30
  inline self& operator=(const double& rhs) {
31
    parent::operator=(rhs);
32
    return *this;
33
  }
34
}

Wenn's mir einfach nur darum ginge, dass es funktioniert, würde ich 
einfach die zweite Version nehmen. Aber ich möchte wissen, wie ich das 
richtig machen muss.

Ich bin dankbar für jeden Hinweis.
lg

von Chris F. (chfreund) Benutzerseite


Lesenswert?

Im Template ist doch fast alles öffentlich, was genau willst Du denn 
erreichen?

von The D. (thedaz)


Lesenswert?

Wenn du möchtest, dass tester identisch mit fixedpoint<100> ist, dann 
benutze einfach ein typedef tester fixedpoint<100>. Wenn du aber 
vorhast, der class tester irgendwelche Attribute hinzuzufügen, dann wäre 
das automatische Kopieren bzw. Vererben der parent class Konstruktoren 
und Operatoren schonmal falsch, denn die können ja nichts von den extra 
Attributen wissen. Aus diesem Grund werden Konstruktoren auch nicht 
vererbt.

von Rasputin (Gast)


Lesenswert?

The D. schrieb:
> Wenn du möchtest, dass tester identisch mit fixedpoint<100> ist,
> dann
> benutze einfach ein typedef tester fixedpoint<100>.
Das ist mir bewusst (auch wenn's eher umgekehrt heissen müsste ;) )

> Wenn du aber
> vorhast, der class tester irgendwelche Attribute hinzuzufügen, dann wäre
> das automatische Kopieren bzw. Vererben der parent class Konstruktoren
> und Operatoren schonmal falsch, denn die können ja nichts von den extra
> Attributen wissen. Aus diesem Grund werden Konstruktoren auch nicht
> vererbt.

Angenommen, ich möchte (später) der Klasse tester zusätzliche Methoden 
und Eigenschaften verpassen, die jedoch keine eigene Initialisierung 
benötigen. Darum möchte ich alles inkl. constructoren 1:1 erben. Laut 
allem, was ich bisher im netz gelesen habe, müsste das mit using 
fixedpoint::fixedpoint; möglich sein. Ist aber nicht :((

von Rüdiger (Gast)


Lesenswert?

Rasputin schrieb:
> Laut
> allem, was ich bisher im netz gelesen habe, müsste das mit using
> fixedpoint::fixedpoint; möglich sein. Ist aber nicht :((

Und wieso nicht? Welche Fehlermeldung? Welcher Compiler?...

Ab C++11 kann man genau so Konstruktoren vererben.

von Rasputin (Gast)


Lesenswert?

Als Compiler verwende ich im Moment (der faulheit halber) Microsoft 
Visual Studio Express 2013. Mir ist allerdings wichtig, dass die Lösung 
standardkonform ist und plattformübergreifen funktioniert.

Fehlermeldung ist folgende:
1
Fehler  1  error C2440: 'Initialisierung': 'int' kann nicht in 'tester' konvertiert werden  d:\[...]\test\test\test.cpp  50  1  Test
2
Fehler  2  error C2440: 'Initialisierung': 'double' kann nicht in 'tester' konvertiert werden  d:\[...]\test\test\test.cpp  51  1  Test
3
  3  IntelliSense: Für eine Konvertierung von ""int"" in""tester"" ist kein passender Konstruktor vorhanden.  d:\[...]\Test\Test\Test.cpp  50  13  Test
4
  4  IntelliSense: Für eine Konvertierung von ""double"" in""tester"" ist kein passender Konstruktor vorhanden.  d:\[...]\Test\Test\Test.cpp  51  13  Test

von Rasputin (Gast)


Lesenswert?

Hier noch die Zeilennummern:
1
Zeile int main() {
2
  tester t;
3
  t = 50.4949;
4
  tester q = 1; // <<- Zeile 50
5
  tester r = 0.01; // <<- Zeile 51
6
  q = t + 1.2;
7
  q += 1;
8
  q.print();
9
10
  while (1)
11
    _sleep(1);
12
}

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Wenn Du tester von fixedpoint ableiten würdest, statt von 
fixedpoint<100>, hättest Du vermutlich mehr Chancen.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Rasputin schrieb:
> Ich bin dankbar für jeden Hinweis.

Wenn es C++03 sein muss:
1
class tester : public fixedpoint<100> {
2
public:
3
  explicit tester() : fixedpoint() {}
4
5
  template < class T >
6
  explicit tester( const T& init ) : fixedpoint( init ) {}

Ansonsten:
- Hinter einer Funktionsdefinition muss kein Semikolon stehen.
- für `protected` gibt es nur ganz wenige Anwendungsfälle
- das `self` aus Pascal kennen nur Leute, die Pascal kennen; in C++ wird 
so etwas üblicherweise nicht verwendet.
- Du kannst `unsigned int` auch mit `unsigned` abkürzen, dann brauchst 
Du  `uint` nicht.
- Verwende lieber die C++ Header, also <cstdlib>
- Wenn Du Wert auf Standar-Konformität legst, solltest Du nicht "#pragma 
once" verwenden.
- "move assignement" ergibt für Deinen fixedpoint Typen überhaupt keinen 
Sinn!
- Verwende in Construktoren Initialisierung, keine Zuweisung!
- Wenn Du in einer Klassendefinition eine Funktionsdefinition hast, dann 
ist die automatisch `inline`.
- usw. ;-)

mfg Torsten

von Mikro 7. (mikro77)


Angehängte Dateien:

Lesenswert?

Rasputin schrieb:
> Als Compiler verwende ich im Moment (der faulheit halber) Microsoft
> Visual Studio Express 2013. Mir ist allerdings wichtig, dass die Lösung
> standardkonform ist und plattformübergreifen funktioniert.

Also mit gcc (Ubuntu 4.8.4-2ubuntu1~14.04.1) kompiliert das und ist auch 
ausführbar (nach korrigierten Kleinigkeiten). Liegst am MS Compiler?!

von HochwertigeFüllungFeinstenWeichstenFlaums (Gast)


Lesenswert?

Mikro 7. schrieb:
> Rasputin schrieb:
>> Als Compiler verwende ich im Moment (der faulheit halber) Microsoft
>> Visual Studio Express 2013. Mir ist allerdings wichtig, dass die Lösung
>> standardkonform ist und plattformübergreifen funktioniert.
>
> Also mit gcc (Ubuntu 4.8.4-2ubuntu1~14.04.1) kompiliert das und ist auch
> ausführbar (nach korrigierten Kleinigkeiten).

Mit VS 2015 (ohne Update 2) auch. unistd.h musste natürlich raus.

von Rasputin (Gast)


Lesenswert?

Vielen Dank an alle! Also liegts anscheinend am Compiler :( .  Dann 
hätte ich mir die grauen Haare wohl sparen können, wenn ich's mal auf 
meinem Raspberry probiert hätte... hmpf

Und ich hab eben gesehen, dass es auch eine Express Version von VS2015 
gibt. Ich dachte bis anhin, VS2013 Express wäre die aktuellste :/

Torsten R. schrieb:
> Rasputin schrieb:
>> Ich bin dankbar für jeden Hinweis.
Danke für die Durchsicht meines Codes und die Tipps :)

> Wenn es C++03 sein muss:
> class tester : public fixedpoint<100> {
> public:
>   explicit tester() : fixedpoint() {}
>
>   template < class T >
>   explicit tester( const T& init ) : fixedpoint( init ) {}
>
Interessante Idee. Mit "automatischen Templates" (keine Ahnung wie sich 
das richtig nennt) hab ich bisher noch nicht gearbeitet.

> Ansonsten:
> - Hinter einer Funktionsdefinition muss kein Semikolon stehen.
Au ja, übersehen ;)
> - für `protected` gibt es nur ganz wenige Anwendungsfälle
Ich bin normalerweise eher paranoid was die Sichtbarkeit angeht, aber in 
diesem Fall, wenn das direkte Ändern einer Variable keine negativen 
Auswirkungen auf die Datenkonsistenz haben kann, war ich grosszügig ;)
> - das `self` aus Pascal kennen nur Leute, die Pascal kennen; in C++ wird
> so etwas üblicherweise nicht verwendet.
Ich hab das aus PHP übernommen, da ich damit viel arbeite ;) Immer 
wieder die eigene Klasse ausschreiben zu müssen, war mir zu nervig. Dann 
ist mir plötzlich ein licht aufgegangen, wie ich das auch in C++ 
implementieren könnte. ;) Ist das unschöner Programmierstil?
Ausserdem hatte ich die Hoffnung, damit etwas realisieren zu können, was 
in PHP "late static binding" genannt wird. Wobei ich gar noch nicht 
sicher bin, ob das in C++ überhaupt notwendig ist. Muss noch etwas 
rumforschen ;)
> - Du kannst `unsigned int` auch mit `unsigned` abkürzen, dann brauchst
> Du  `uint` nicht.
Wusste ich nicht. Danke.
> - Verwende lieber die C++ Header, also <cstdlib>
Wusste ich auch nicht. Was ist genau der Unterschied?
> - Wenn Du Wert auf Standar-Konformität legst, solltest Du nicht "#pragma
> once" verwenden.
Erwischt ;) Mache ich normalerweise auch, nur steht das bei VS 
automatisch drin, und hier war ich zu faul, es zu ändern. ;)
> - "move assignement" ergibt für Deinen fixedpoint Typen überhaupt keinen
> Sinn!
Klar, aber sollte man ihn nicht trotzdem definieren? Wegen der Rule of 
Five?
> - Verwende in Construktoren Initialisierung, keine Zuweisung!
Ist das auch "erlaubt" mit Berechnungen? Also im konkreten Fall 
"fixedpoint(const uint& v, const uint& s) : value(v * FPL) + s)" Habe 
das noch nirgends so gesehen. Aber das wäre super, ich wollte nähmlich 
auch noch eine constFixedpoint klasse ableiten, in der value const ist.
> - Wenn Du in einer Klassendefinition eine Funktionsdefinition hast, dann
> ist die automatisch `inline`.
War mir auch nicht bekannt. Ich wusste nur, dass wenn die Funktion in 
einer externen cpp-Datei definiert ist, sie nie "nach aussen" inline 
sein kann.
> - usw. ;-)

von A. H. (ah8)


Lesenswert?

Ich weiß zwar nicht genau, was Du vorhast, würde aber mal die 
grundsätzliche Frage in den Raum stellen wollen, ob die Vererbung in 
diesem Fall das richtige programmiersprachliche Mittel ist. Deine 
fixedpoint Klasse folgt ja so ziemlich zweifelsfrei einer 
Value-Semantik, verhält sich also wie ein mathematischer Zahlenbereich. 
Jetzt kann man Zahlenbereiche durchaus zueinander in Beziehung setzten 
im Sinne von „ist ein“, das Problem ist nur, dass diese Beziehung genau 
der entgegengesetzten Richtung dessen folgt, was man in C++ 
implementieren kann.

Mathematisch gesehen ist zum Beispiel jede reelle Zahl auch eine 
komplexe Zahl, d.h. überall, wo man eine komplexe Zahl verwenden kann, 
kann erst recht eine reelle Zahl stehen. Im Sinne eines 
Typ-Polymorphismus müsste also Real von Complex erben. Und tatsächlich 
gibt es eine Eigenschaft, die in dieser Richtung hinzu kommt, nämlich 
die Wohlordnung; die Klasse Real würde also zusätzlich die 
Vergleichsoperatoren (<,<=,>,>=) implementieren.

Programmiertechnisch ist es allerdings genau umgekehrt, denn 
grundsätzliche hat die Klasse Complex mehr und komplexere Funktionen und 
auch mehr Elemente als die Klasse Real, das in diesem Fall 
offensichtliche Beispiel ist der Imaginärteil. Wollte man die einfachere 
Implementierung von Real durch hinzufügen von Attributen zur 
Implementierung der komplexeren Klasse Complex nachnutzen, dann müsste 
man Complex von Real erben lassen. Das entspricht aber nicht der 
mathematischen Realität, denn längst nicht überall, wo eine reelle Zahl 
erlaubt ist, darf auch eine komplexe Zahl stehen.

Nun würde allerdings kein Mensch auf die Idee kommen, reelle Zahlen als 
komplexe Zahlen zu implementieren, genauso wenig wie gebrochen Zahlen 
als reelle oder ganze Zahlen als gebrochene. Man implementiert 
Zahlenbereiche daher in der Regel unabhängig von einander und stellt die 
„ist ein“ Beziehung, sofern nötig, durch implizite Typumwandlung des 
einfacheren in den komplexeren Typ dar. Vererbung ist hier generell ein 
problematischer Ansatz. Wenn, dann erbt der komplexere Typ privat vom 
einfacheren, dann hättest Du aber genau die Schreiberei, die Du gerne 
vermeiden möchtest.

Wie gesagt, ich weiß nicht genau, was Du vor hast, aber vielleicht ist 
das ja das Problem, über das Du hier gerade stolperst.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Rasputin schrieb:

> Interessante Idee. Mit "automatischen Templates" (keine Ahnung wie sich
> das richtig nennt)

einfach template, oder template function.

> Ich bin normalerweise eher paranoid was die Sichtbarkeit angeht, aber in
> diesem Fall, wenn das direkte Ändern einer Variable keine negativen
> Auswirkungen auf die Datenkonsistenz haben kann, war ich grosszügig ;)

paranoid (also der default) wäre `private`, grosszügig wäre `public`. 
protected ist für Schnittstellen zu ableitenden Klassen.

> Ist das unschöner Programmierstil?

Nein, nur "ungewöhnlich".

>> - Verwende lieber die C++ Header, also <cstdlib>
> Wusste ich auch nicht. Was ist genau der Unterschied?

Alle Typen und Funktionen sind im namespace std und damit sind die 
header dann konsistent zu den anderen C++ headern.

>> - "move assignement" ergibt für Deinen fixedpoint Typen überhaupt keinen
>> Sinn!
> Klar, aber sollte man ihn nicht trotzdem definieren? Wegen der Rule of
> Five?

Du braucht keine der 5! Lass den Compiler einfach seine Arbeit machen. 
Weniger Code enthält weniger Fehler und ist leichter zu lesen.

>> - Verwende in Construktoren Initialisierung, keine Zuweisung!
> Ist das auch "erlaubt" mit Berechnungen? Also im konkreten Fall
> "fixedpoint(const uint& v, const uint& s) : value(v * FPL) + s)"

Auf jeden Fall! Gerade wenn `value` const sein soll, musst Du den member 
auch initialisieren.

>> - usw. ;-)

- Binäre Operatoren wie + implementiert man üblicherweise als freie 
Funktion. Das hat den Hintergrund, dass für den Fall, das es eine 
implizite Konvertierung zu Deinem Typen gibt, diese auch angewendet 
werden kann, wenn dieses konvertierbare Argument auf der linken Seite 
des Operators steht. Implizite Konvertierungen können aber recht schnell 
zur Plage werden.

Für den Fall, dass das ganze nicht nur eine Übung sein soll. Sondern für 
den Fall, dass Du einen Integer Wert hast, für den nur bestimmte 
Operationen zugelassen sein sollen: Da gibt es was von Boost, das die 
Implementierung der Operationen vereinfacht: 
http://www.boost.org/doc/libs/1_60_0/libs/utility/operators.htm

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.