Hallo MC,
ich zerbreche mir gerade den Kopf, wie man in C++ Header sinnvoll
inkludiert. Das Problem ist folgendes:
auto.h:
1
#include "reifen.h"
2
3
class auto
4
{
5
public:
6
auto();
7
~auto();
8
9
int geschw();
10
int ort_x();
11
int ort_y();
12
int ort_x();
13
14
private:
15
int geschw;
16
int x,y,z;
17
18
reifen r1;
19
reifen r2;
20
reifen r3;
21
reifen r4;
22
}
Wenn ich nun irgendwo im Hauptprogramm "Auto.h" inkludiere, dann sollen
nur die public-Funktionen verwendbar sein. Aber ich inkludiere ja
"reifen.h" gleich mit, d.h. dass im Hauptprogramm auch Reifen
instantiiert werden können, die sollen aber verborgen sein.
"reifen.h" in "auto.h" zu inkludieren, macht m.M.n. Sinn, aber wie
struktiere ich es so, dass die Reifen verborgen bleiben?
Gruß,
Alex
Ja, aber es wird nicht kompilieren:
1. "auto" ist ein geschützter Begriff.
2. Ich hatte das Beispiel nicht richtig angeschaut. Die forward
declaration funktioniert nur, wenn du als Klassen-Member Pointer auf
reifen* benutzt.
Axel471 schrieb:> Wenn ich nun irgendwo im Hauptprogramm "Auto.h" inkludiere, dann sollen> nur die public-Funktionen verwendbar sein. Aber ich inkludiere ja> "reifen.h" gleich mit, d.h. dass im Hauptprogramm auch Reifen> instantiiert werden können, die sollen aber verborgen sein.
Das sind aber zwei verschiedene Dinge ... Die privaten Member von "auto"
(BTW: Bitte nicht wirklich diesen Namen verwenden ...) sind ja trotzdem
nur klassenintern verwendbar - da kann der Benutzer so viele Reifen
erzeugen, wie er will.
Axel471 schrieb:> So etwa?
Nicht direkt, das funktioniert so (Vorausdeklaration) nur mit Zeigern
und Referenzen, da der Compiler sonst die Größe des Typs "reifen" kennen
muss und du damit wieder da bist, wo du angefangen hast.
Alternativ: Suche mal nach "PIMPL".
Würdest Du "reifen" als Pointer verwenden, würde eine
Vorwärtsdeklaration von "reifen" genügen, auch wenn Du ein Objekt vom
Typ NichtAuto instanziierst. Wenn Du aber "reifen" so verwendest, wie in
Deinem Beispiel, muss für das Instanziieren eines Objekts vom Typ
NichtAuto auch "reifen" bekannt sein, da andernfalls die Größe von
NichtAuto nicht bekannt ist.
Danke für die Hilfe.
"pimple" gefällt mir gut. Nur dass man ein mal dynamisch Speicher
allokieren muss ist suboptimal, aber man kann nicht alles haben.
Das ist aber kein PIMPL. ;)
Der Sinn von PIMPL ist ja schon auch ein bisschen, dass man
Binärkompatibilität wahrt wenn man zum Beispiel Klassenmember hinzufügt
oder so. Dazu muss man natürlich dynamische Speicherverwaltung nutzen.
Sven B. schrieb:> Das ist aber kein PIMPL. ;)
Die ursprüngliche Aufgabenstellung war:
> d.h. dass im Hauptprogramm auch Reifen> instantiiert werden können, die sollen aber verborgen sein.
dazu braucht man kein pimpl.
Das löst zwar das, was der TO oben als Problem beschrieben hat. Aber es
dürfte oft zu kurz gesprungen sein, da ich jetzt die class reifen bei
class fahrrad, class schubkarre,... jeweils wieder neu erfinden muss.
Gerd E. schrieb:> Aber es> dürfte oft zu kurz gesprungen sein, da ich jetzt die class reifen bei> class fahrrad, class schubkarre,... jeweils wieder neu erfinden muss.
Ja, genau so oft, wie Auto- und Rad-Klassen halt in der Praxis
tatsächlich verwendet werden ;-)
Ist halt ganz schwierig über mögliche Anforderungen an offensichtlich
praxis-fernen Beispielen zu diskutieren ;-) Ich würde beim konkreten
Beispiel auch nicht auf die Idee kommen, den Einsatz von "reifen" an
anderer Stelle zu unterbinden.
Gerd E. schrieb:> Das löst zwar das, was der TO oben als Problem beschrieben hat. Aber es> dürfte oft zu kurz gesprungen sein, da ich jetzt die class reifen bei> class fahrrad, class schubkarre,... jeweils wieder neu erfinden muss.
Dann leitet man fahrrad und schubkarre von einer gemeinsamen Basisklasse
fahrzeug ab und gibt der die Reifen. Man kann diese ja auch protected
machen.
Allerdings verstehe ich die Ursprungsfrage nicht so ganz:
Axel471 schrieb:> "reifen.h" in "auto.h" zu inkludieren, macht m.M.n. Sinn, aber wie> struktiere ich es so, dass die Reifen verborgen bleiben?
Warum? Wenn ich einen Reifen instanziieren will, kann ich ja auch
einfach selber reifen.h inkludieren. Anders gefragt: Was genau ist das
Ziel und welches Szenario soll damit verhindert werden?
Verstehe das eigentliche Problem auch nicht wirklich.
Warum nicht von Reifen/Motor/Lenkrad usw. usf. erben ?
Dafür ist doch OOP da ?
Oder die passenden Klassen in eine
1
private
Deklaration einbinden.
Sinn macht mehr wenn Du erbst und die Dinge die Du nutzen willst, wie
Reifenluftdruck u.ä. in der Klasse Reifen
1
private
deklarierst und einen passenden setter und getter programmierst.
Dann kann z.B. das Objekt Autocomputer den Luftdruck anzeigen während
das Objekt Schubkarre das halt nicht braucht ...
Ein javaesker Ansatz wäre ein abstraktes Interface Auto (nur mit den
relevanten Methoden und ohne Member) und eine simple factory, die ein
richtiges AutoObjekt erstellt, aber einen Basiszeiger auf ein Auto
liefert.
Car.h:
1
#ifndef _CAR_H_
2
#define _CAR_H_
3
4
classCar
5
{
6
public:
7
virtual~Car()
8
{
9
}
10
virtualintgeschw()const=0;
11
};
12
13
#endif
RealCar.h:
1
#ifndef _REAL_CAR_H_
2
#define _REAL_CAR_H_
3
4
#include"Car.h"
5
#include"Tire.h"
6
7
classRealCar:publicCar
8
{
9
private:
10
RealCar();
11
RealCar(constRealCar&other);
12
13
Tiretire;
14
15
public:
16
intgeschw()const;
17
friendclassCarFactory;
18
};
19
20
#endif
RealCar.cpp:
1
#include"RealCar.h"
2
3
RealCar::RealCar()
4
{
5
}
6
7
intRealCar::geschw()const
8
{
9
return23*tire.radius();
10
}
Tire.h:
1
#ifndef _TIRE_H_
2
#define _TIRE_H_
3
4
classTire
5
{
6
public:
7
intradius()const;
8
};
9
10
#endif
Tire.cpp:
1
#include"Tire.h"
2
3
intTire::radius()const
4
{
5
return42;
6
}
CarFactory.h:
1
#ifndef _CAR_FACTORY_H_
2
#define _CAR_FACTORY_H_
3
4
#include"Car.h"
5
6
classCarFactory
7
{
8
public:
9
staticCar*make_car();
10
};
11
12
#endif
CarFactory.cpp:
1
#include"CarFactory.h"
2
#include"Car.h"
3
#include"RealCar.h"
4
5
Car*CarFactory::make_car()
6
{
7
RealCar*p=newRealCar;
8
returnp;
9
}
main.cpp:
1
#include"Car.h"
2
#include"CarFactory.h"
3
4
#include<iostream>
5
6
intmain()
7
{
8
Car*mycar=CarFactory::make_car();
9
std::cout<<"geschw ist "<<mycar->geschw()<<"\n";
10
deletemycar;
11
12
/* Tire foo; kompiliert nicht */
13
14
return0;
15
}
In echt würde man statt roher Pointer sinnvollerweise irgendwelche Smart
Pointer benutzen.
Ob das in diesem Fall den gigantischen Komplexitätsgewinn wert ist, ist
sehr stark zu bezweifeln. Sinnvoll ist das nur, wenn es mehrere
Autoarten gibt und CarFactory irgendwie entscheidet, welches sie
liefert:
Axel471 schrieb:> Wenn ich nun irgendwo im Hauptprogramm "Auto.h" inkludiere, dann sollen> nur die public-Funktionen verwendbar sein. Aber ich inkludiere ja> "reifen.h" gleich mit, d.h. dass im Hauptprogramm auch Reifen> instantiiert werden können, die sollen aber verborgen sein.>> "reifen.h" in "auto.h" zu inkludieren, macht m.M.n. Sinn, aber wie> struktiere ich es so, dass die Reifen verborgen bleiben?
Kurze Antwort: C++ ist auf Performance ausgelegt. Statt zu verpointern
wird ein zusammenhängender Speicherbereich gewählt. Dem Anwender steht
es natürlich trotzdem frei (wie Rufus Τ. Firefly bereits schrieb),
selbst zu verpointern (und damit zu verbergen).
Etwas auführlich findest Du das bei Scott Meyers, bspw. hier online:
http://debian.fmi.uni-sofia.bg/~mrpaff/Effective%20C++/EC/EI34.HTM
(nicht vom Titel irritieren lassen).
Tom schrieb:> In echt würde man statt roher Pointer sinnvollerweise irgendwelche Smart> Pointer benutzen.
Ja, dann zeig einem Anfänger doch nicht erst, wie man es nicht macht.
;-)
Irgend welche Smart Pointer wäre bis C++11 std::auto_ptr und ab C++11
std::unique_ptr (liegen jedem Compiler bei, gibt überhaupt keinen Grund,
die nicht zu verwenden).
In C++ (und in C ist das glaube ich auch so), gilt: Bezeichner die mit
zwei Unterstrichen, oder Bezeichner die mit einem Unterstrich beginnen,
gefolgt von einem Großbuchstaben anfangen, gehören dem Compiler und
dürfen von uns nicht verwendet werden.
Torsten R. schrieb:> Irgend welche Smart Pointer wäre bis C++11 std::auto_ptr und ab C++11> std::unique_ptr (liegen jedem Compiler bei, gibt überhaupt keinen Grund,> die nicht zu verwenden).
Naja, es gibt schon Gründe, std::auto_ptr in diesem Kontext nicht zu
verwenden.
Rolf M. schrieb:> Naja, es gibt schon Gründe, std::auto_ptr in diesem Kontext nicht zu> verwenden.
Welche Gründe würdest Du sehen, bei einer Factory (der Kontext, den Tom
vorgegeben hat) nicht std::auto_ptr zu verwenden? (vorrausgesetzt, die 4
Jahre alte C++11 Version kann nicht verwendet werden) Sicher hat
std::auto_ptr seine Macken, aber immer noch besser als nix.
cppler schrieb:> Verstehe das eigentliche Problem auch nicht wirklich.> Warum nicht von Reifen/Motor/Lenkrad usw. usf. erben ?> Dafür ist doch OOP da ?
Das ist meistens keine gute Idee.
Halte dich beim (public) Vererben an die einfache Regel, dass eine
derartige Vererbung eine "Ist ein" Beziehung ausdrueckt.
Frag dich also einfach mal, ob ein Auto (auch im weitesten Sinne) eine
Form eines Reifens ist. Wenn die Antwort Nein lautet, dann ist Vererbung
in der ueberwiegenden Mehrzahl der Fälle keine gute Idee.
Wenn du draufkommst, dass du eine "Hat ein" Beziehung beschreiben
willst, dann ist die Einführung einer Klassen-Member Variablen das
Mittel der Wahl
Ich denke mal der TO will einfach Klassen-Internas verbergen.
Ich hatte z.B. mal eine Klasse die eine Serielle Verbindung verwaltet
erstellt.
Diese benutzt intern ganz oldschool termios.h.
Die daraus resultierenden Membervariablen wollte ich auch vom Benutzer
verstecken.
Ebenso die benutzte Ringpufferklasse.
Man mag ja gerne die konkrete Implementierung hinter einem statischen
Interface verbergen, das gleich bleibt auch wenn sich Internas ändern.
Karl H. schrieb:> cppler schrieb:>> Verstehe das eigentliche Problem auch nicht wirklich.>> Warum nicht von Reifen/Motor/Lenkrad usw. usf. erben ?>> Dafür ist doch OOP da ?>> Das ist meistens keine gute Idee.>> Halte dich beim (public) Vererben an die einfache Regel, dass eine> derartige Vererbung eine "Ist ein" Beziehung ausdrueckt.> Frag dich also einfach mal, ob ein Auto (auch im weitesten Sinne) eine> Form eines Reifens ist. Wenn die Antwort Nein lautet, dann ist Vererbung> in der ueberwiegenden Mehrzahl der Fälle keine gute Idee.> Wenn du draufkommst, dass du eine "Hat ein" Beziehung beschreiben> willst, dann ist die Einführung einer Klassen-Member Variablen das> Mittel der Wahl
Hatte ich das nicht auch in Erwägung gezogen oder habe ich mich falsch
ausgedrückt ?
Ich würds aber trotzdem so machen weil ich da mehr Flexibiltät habe,
auch wenn es wie Du schreibst ein Dilemma aufwirft.
Wie würdest Du das denn in z.B. UML darstellen ?
Ist halt wieder die Frage ob das Rad vor dem Ei "erfunden" wurde ;-)
Aber Du schreibst ja "meistens" ;-)
OOP ist nicht so einfach und es gibt viele Wege nach Rom, einer ist was
ich geschrieben habe.
Und warum bin ich noch nicht inner Heia ?
Zuviel Kaffee kann schonmal zu irgendwas führen ...
cppler schrieb:> OOP ist nicht so einfach und es gibt viele Wege nach Rom,
In dem Fall nicht. KHB hat schon recht.
In deinem Beispiel handelt es sich um eine Ist-Teil-von-Beziehung, da
ist Vererbung völlig fehl am Platz.
cppler schrieb:> Wie würdest Du das denn in z.B. UML darstellen ?
In UML ist diese Art der Beziehung ganz genau definiert. Stichwort:
Komposition.
Klar, man kann auch alles von jedem erben lassen. Klappen wirds schon
irgendwie...
cppler schrieb:> Ich würds aber trotzdem so machen weil ich da mehr Flexibiltät habe,
In welcher Hinsicht? Das mit der Darstellung in UML verstehe ich auch
nicht. Man kann Komposition, Aggregation, Ableitung und mehr darstellen.
cppler schrieb:> Warum nicht von Reifen/Motor/Lenkrad usw. usf. erben ?
Weil es sowohl vom OO-Design her als auch aus technischen Gründen eine
ausgesprochen schlechte Idee ist.
cppler schrieb:> Warum nicht von Reifen/Motor/Lenkrad usw. usf. erben ?
Ist der Beitrag Satire?
bal schrieb:> Klar, man kann auch alles von jedem erben lassen.
;)
Auto erbt dann viermal von Reifen. Mist, geht nicht, also anders:
1
classReifenVL
2
{
3
// void pumpen(); mist, das waere spaeter nicht eindeutig, also anders:
cppler schrieb:> Ich würds aber trotzdem so machen weil ich da mehr Flexibiltät habe,> auch wenn es wie Du schreibst ein Dilemma aufwirft.
Das ergibt dann genau die Programme, die keiner warten will und
deretwegen C++ einen schlechten Ruf hat. Dabei ist gerade dieser Teil
des Hierarchiedesigns relativ simpel und straigth forward. Hält man sich
daran, ergibt das Klassenhierarchien die leicht zu durchschauen, leicht
zu verwenden und leicht zu ändern sind. Hält man sich nicht daran, artet
das über kurz oder lang in einen Krampf aus.
Ein PKW ist nun mal kein Lenkrad und ein PKW ist kein Motor. Ein PKW
ist auch keine Windschutzscheibe und es ist auch kein Blinker. Aber
es hat all diese Dinge.
Allerdings ist ein PKW ein Kraftfahrzeug. Und ein Kraftfahrzeug ist
ein Fahrzeug dessen Merkmal darin besteht, dass ein Kraftfahrzeug per
Definition einen Motor hat. Auch ein LKW ist ein Kraftfahrzeug. Da
ein Kraftfahrzeug über einen Motor verfügt, folgt daraus automatisch,
dass auch ein LKW einen Motor hat. Womit auch klar gestellt ist, dass
der Motor zum Kraftfahrzeug gehört, wenn man in seiner Modellierung die
Hierarchie bis mindestens zum Kraftfahrzeug oder noch weiter hinauf
modellieren muss, weil man auch Schiffe und Flugzeuge modellieren
möchte.
Womit allerdings noch lange nicht gesagt ist, welcher Art dieser Motor
ist. Ein Kraftfahrzeug hat einen. Punkt. Konkret kann das ein Benziner
oder ein Dieselmotor sein. Das ist wiederrum dem Kraftfahrzeug egal, es
hält einfach nur einen Pointer auf den Motor (muss ein Pointer sein,
damit virtuelle Methodenaufrufe funktionieren). Das Kraftfahrzeug
startet einfach nur den Motor. Wie das konkret geht, dass weiss dann
jeder Motortyp für sich selbst, das hat das Kraftfahrzeug nicht zu
interessieren.
Kommt dann allgemein ein mit Erdgas betriebener Motor zur Flotte hinzu,
dann genügt es, einen entsprechende Klasse von der Motor-Klasse
herzuleiten und die 'Start' Methode zu definieren. An den
Kraftfahrzeugen ändert sich genau gar nichts. Die rufen weiterhin
einfach nur die Start-Methode auf. Denn wie das genau geht, das weiss ja
jeder Motor für sich selbst. Der Dieselmotor wird seine Vorglühkerzen
aktivieren, während der Benziner die Benzinpumpe einschaltet und ein
Elektromotor gar nichts tun muss.
Tom schrieb:> class Auto: public ReifenVL, public ReifenVR, public ReifenHL, public> ReifenHR, public Motor, public Lenkrad> {> };> So macht man flexible SW-Architektur richtig.
Hm, kein Reserverad? Dann bitte alle Reifen von RunFlats ableiten!
Karl H. schrieb:> ...> Womit auch klar gestellt ist, dass der> Motor zum Kraftfahrzeug gehört, wenn man in seiner Modellierung die> Hierarchie bis mindestens zum Kraftfahrzeug oder noch weiter hinauf> modellieren muss, weil man auch Schiffe und Flugzeuge modellieren> möchte.> Womit allerdings noch lange nicht gesagt ist, welcher Art dieser Motor> ist. Ein Kraftfahrzeug hat einen. Punkt.
Hm, eher Semikolon. Ein Kraftfahrzeug hat mindestens einen, würde ich
eher sagen. Sonst stehen alle Toyota Prius und Opel Ampera dumm da.
beric schrieb:> Karl H. schrieb:>> ...>> Womit auch klar gestellt ist, dass der>> Motor zum Kraftfahrzeug gehört, wenn man in seiner Modellierung die>> Hierarchie bis mindestens zum Kraftfahrzeug oder noch weiter hinauf>> modellieren muss, weil man auch Schiffe und Flugzeuge modellieren>> möchte.>> Womit allerdings noch lange nicht gesagt ist, welcher Art dieser Motor>> ist. Ein Kraftfahrzeug hat einen. Punkt.>> Hm, eher Semikolon. Ein Kraftfahrzeug hat mindestens einen, würde ich> eher sagen. Sonst stehen alle Toyota Prius und Opel Ampera dumm da.
Es sei denn du führst einen Motortyp 'Hybrid' ein, der aus dem
entsprechenden Management und 2 weiteren Motoren besteht :-)
ABer du hast schon recht. Man sollte dann nicht mehr davon sprechen,
dass ein Kraftfahrzeug einen Motor hat, sondern es hat einen Antrieb.
Lässt sich alles modellieren, wenn es denn sein muss.
Aber Ausgangspunkt ist ist die Unterscheidung 'ist ein' und 'hat ein'
und wie man diese Beziehungen in den meisten Fällen sinnvoll im
Hierarchiedesign unterbringt.
Interssant wäre zb auch die Fragestellung, ob ein Fahrrad ein
Kraftfahrzeug darstellt mit einem Antriebstyp 'Mensch'. Bzw. ein
Hybridantrieb, der die beiden Antriebe 'elektrisch' und 'Mensch'
zusammenfasst - ein E-Bike.
Hmm. Die Hierarchie scheint so gut zu funktionieren und das ist immer
ein gutes Zeichen, wenn sich auch Dinge modellieren lassen (mglw. mit 1
oder 2 zusätzlichen Klassen, die nahtlos ins Design passen und auch
nachträglich mit wenig Problemen einschieben lassen), an die man vorher
gar nicht gedacht hat und ohne dass man 70% des ganzen Programms umbauen
muss.
Karl H. schrieb:> Interssant wäre zb auch die Fragestellung, ob ein Fahrrad ein> Kraftfahrzeug darstellt mit einem Antriebstyp 'Mensch'.
Dann wird es langsam relevant, was man von dem Modell eigentlich will.
Die ganze Realität ist zu komplex zum Modellieren.
Wenn ich Leisure Suit Larry 17 programmiere, erfüllt ein Fahrzeug andere
Aufgaben¹ als bei einem Tool, das die gleichzeitigen Reisen von 10000
Vertretern optimiert auf den vorhandenen Fuhrpark und andere
Verkehrsmittel verteilt². In beiden Fällen interessiert mich der Motor
überhaupt nicht. Anders bei einem Rennsimulatorspiel, bei dem man ein
Fahrzeug aus Komponenten zusammenstellen kann. Oder einem
Autoquartettgenerator. Oder einem Tool, das Benzinpreise unter
Berücksichtigung der Urlaubswellen und Staumeldungen zentral optimiert.
In allen Fällen wird eine Klasse "Fahrzeug" völlig anders aussehen; in
keinem Fall wird sie aus Motor und 4 Rädern bestehen.
Nichtwissen macht frei und flexibel. Wenn der Vertreterplaner nur mit
generischen Transportmitteln² arbeitet, von deren innerer Funktion und
Komponenten er nichts weiß, kann ich dem Algorithmus in der übernächsten
Version ein Beamgerät unterschieben, ohne etwas zu ändern. Und Larry
kann in Teil 523 ohne große Änderungen mit einem Hoverboard oder einem
Spacepony (beides von ¹ abgeleitet) vor der Disko auftauchen.
beric schrieb:> Hm, kein Reserverad? Dann bitte alle Reifen von RunFlats ableiten!
Gegenvorschlag: Jede Reifenklasse bekommt ein Feld mit einem Status-enum
{OK, PLATT, GEKLAUT, IST_EIN_RESERVERAD}. Dazu gibt es einen globalen
ReifenManager (natürlich ein Singleton), der das alles verwaltet und der
sich auch merkt, ob noch ein Reserverad im Kofferraum ist. Das ist
flexibler.