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
typedefunsignedintuint;
2
3
#include"fixedpoint.h"
4
#include<stdlib.h>
5
6
classtester:publicfixedpoint<100>{
7
protected:
8
typedeftesterself;
9
typedeffixedpointparent;
10
11
public:
12
usingfixedpoint::fixedpoint;
13
usingfixedpoint::operator=;
14
};
15
16
17
intmain(){
18
testert(5);
19
t=50.4949;
20
testerq=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
classtester:publicfixedpoint<100>{
2
protected:
3
typedeftesterself;
4
typedeffixedpointparent;
5
6
public:
7
inlinetester():fixedpoint(){};
8
inlinetester(constint&v):parent(v){};
9
inlinetester(constuint&v):parent(v){};
10
inlinetester(constfloat&v):parent(v){};
11
inlinetester(constdouble&v):parent(v){};
12
13
inlinetester(constparent&src):parent(src){};
14
15
inlineself&operator=(constint&rhs){
16
parent::operator=(rhs);
17
return*this;
18
}
19
20
inlineself&operator=(constuint&rhs){
21
parent::operator=(rhs);
22
return*this;
23
}
24
25
inlineself&operator=(constfloat&rhs){
26
parent::operator=(rhs);
27
return*this;
28
}
29
30
inlineself&operator=(constdouble&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
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.
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 :((
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.
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
Rasputin schrieb:> Ich bin dankbar für jeden Hinweis.
Wenn es C++03 sein muss:
1
classtester:publicfixedpoint<100>{
2
public:
3
explicittester():fixedpoint(){}
4
5
template<classT>
6
explicittester(constT&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
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?!
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.
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. ;-)
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.
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