Hallo, ich habe folgende Files: foo.c, foo.h bar.c, bar.h Nun wird in foo.h eine Struktur definiert, welche in foo.c verwendet wird. Diese Struktur beinhaltet aber als eines der Elemente eine weitere Struktur, welche in bar.h definiert ist. Wie binde ich die Files korrekt zusammen, sodass der Compiler nichts zu meckern hat? Vielen Dank, Thomas
Thomas Finke schrieb: > Wie binde ich die Files korrekt zusammen, sodass der Compiler nichts zu > meckern hat? Zwei Möglichkeiten: a) in in foo.h selbst bar.h einbinden (bevor das Element aus bar.h verwendet wird) b) in foo.c bar.h vor foo.h einbinden und das überall sonst auch da tun, wo foo.h verwendet wird. Wenn bar.c neben bar.h auch foo.h einbindet, würde im Falle a) foo.h doppelt eingebunden, was zu Fehlern führen kann. Das aber lässt sich mit einem sogenannten include-guard umgehen: foo.h
1 | #ifndef foo_included
|
2 | #define foo_included
|
3 | |
4 | ... das, was vorher schon in foo.h stand |
5 | |
6 | #endif
|
Am elegantesten wäre es vermutlich, eine Datei namens "include.h" anzulegen mit diesem Inhalt:
1 | #ifndef INCLUDE_H
|
2 | #define INCLUDE_H
|
3 | |
4 | #include "bar.h" |
5 | #include "foo.h" |
6 | |
7 | #endif
|
Dann includest du nur diese Datei (die andren .h's nicht) von beiden .c Dateien aus, und hast sämtliche Definitionen überall zur Verfügung.
Das wird aber dann recht unelegant, wenn das Projekt etwas größer wird. Wenn dann nämlich an irgendeinem Header was geändert wird, dauert der Build etwas. Wenn in der Struct foo lediglich ein Zeiger auf bar verwendet wird, ist eine Vorwärtsdeklaration das eleganteste.
Niklas Gürtler schrieb: > Am elegantesten wäre es vermutlich, eine Datei namens "include.h" > anzulegen Das hab ich auch mal geglaubt: Einfach alles inkludieren und nie mehr Sorgen haben. Das stimmt aber nicht. Wenn dein Projekt größer wird oder wenn du mehrere Projekte hast, die sich einen Stamm von gemeinsamen Modulen teilen (Komponentenansatz), dann müssen diese Module so wenig abhängig voneinander sein wie möglich. Jede Kreuz-und-Quer-Inkludierung, die nicht begründet und notwendig ist, muss raus. Du kommst sonst todsicher irgendwann in Teufels Küche. der mechatroniker schrieb: > Das wird aber dann recht unelegant, wenn das Projekt etwas größer wird. > Wenn dann nämlich an irgendeinem Header was geändert wird, dauert der > Build etwas.
> Das hab ich auch mal geglaubt: Einfach alles inkludieren und nie mehr > Sorgen haben. > > Das stimmt aber nicht. Wenn dein Projekt größer wird oder wenn du > mehrere Projekte hast, die sich einen Stamm von gemeinsamen Modulen > teilen (Komponentenansatz), dann müssen diese Module so wenig abhängig > voneinander sein wie möglich. Jede Kreuz-und-Quer-Inkludierung, die > nicht begründet und notwendig ist, muss raus. Du kommst sonst todsicher > irgendwann in Teufels Küche. Findest du? Wenn in einem einzelnen Projekt alles von allem geincluded wird, und jeder alles sieht, ist das einzige Problem, dass das compilieren länger dauert - was man durch precompiled header eingrenzen kann, und das bisschen warten lohnt sich für den geringeren Verwaltungsaufwand. Wenn mehrere Projekte einen gemeinsamen Codeteil verwenden, gehört dieser in eine eigene Lib mit einem abgegrenzten externen Interface, dessen Header-File man dann einbindet, wie man z.B. beim GTKmm nur "#include <gtkmm.h>" schreibt. Oder man programmiert gleich alles in Scala, da hat man dieses Problem eh nicht :-) Der Geschwindigkeitsverlust ist in vielen Fällen ziemlich egal. >> Das wird aber dann recht unelegant, wenn das Projekt etwas größer wird. >> Wenn dann nämlich an irgendeinem Header was geändert wird, dauert der >> Build etwas. Wer hindert dich daran, beim compilen schon das nächste zu coden ;)
Niklas Gürtler schrieb: > Findest du? Wenn in einem einzelnen Projekt alles von allem geincluded > wird, und jeder alles sieht, ist das einzige Problem, dass das > compilieren länger dauert - was man durch precompiled header eingrenzen > kann, und das bisschen warten lohnt sich für den geringeren > Verwaltungsaufwand. Sobald du anfängst, das Konzept Wiederverwendbarkeit (und zwar einfache Wiederverw.) ernst zu nehmen, wirfst du dir mit solchen Methoden selber Prügel zwischen die Füsse.
Wenn du es schaffst, einzelne Klassen, die mit hundert anderen im Projekt zusammenhängen, einfach so "wiederzuverwenden", bitte :) Wenn ein Projekt tatsächlich in mehrere einzeln verwendbare Einheiten zerfällt, kann man das ja so machen, aber auch dann würde ich pro Einheit eine include.h vorsehen die alles davon includet...
Niklas Gürtler schrieb: > Wenn du es schaffst, einzelne Klassen, die mit hundert anderen im > Projekt zusammenhängen, einfach so "wiederzuverwenden", bitte :) Das Problem ist, dass wenn eine einzelne Klasse mit hundert anderen zusammenhängt, dann stimmt schon im Aufbau was nicht :-) Im übrigen: hundert Klassen. Sorry, aber das ist Kinderkram. Du kennst die Projekte nicht, an denen ich beruflich arbeite. Da reden wir von ~600 Source Files mit "ein kleines bischen mehr" als 100 Klassen und rund 700-tausend Lines of Code (so genau weiß das keiner) Und ich sage dir: mit der Vorgehensweise kriegst du das alles nicht mehr gebacken.
> Das Problem ist, dass wenn eine einzelne Klasse mit hundert anderen > zusammenhängt, dann stimmt schon im Aufbau was nicht :-) Die MainWindow Klasse braucht die Viewer Klasse, die braucht die Document Klasse, die braucht die 25 verschiedenen Daten-Objekt-Klassen, die brauchen ihre Basisklassen, Verbindungs-Klassen mit Parametern in Bezug zu den übergeordneten, etc. ... Und natürlich alles wieder in Rückrichtung... Dann erzähl mir mal wie du da was trennen willst :D > Sorry, aber das ist Kinderkram. Du kennst die Projekte nicht, an denen > ich beruflich arbeite. Uhh, **bewunder** wenn ich groß bin erzähl ich dir von meinen :-P Und: Klasse statt Masse. SCNR ;-)
Niklas Gürtler schrieb: >> Das Problem ist, dass wenn eine einzelne Klasse mit hundert anderen >> zusammenhängt, dann stimmt schon im Aufbau was nicht :-) > Die MainWindow Klasse braucht die Viewer Klasse, die braucht die > Document Klasse, die braucht die 25 verschiedenen Daten-Objekt-Klassen, > die brauchen ihre Basisklassen, Verbindungs-Klassen mit Parametern in > Bezug zu den übergeordneten, etc. ... Und natürlich alles wieder in > Rückrichtung... Dann erzähl mir mal wie du da was trennen willst :D Und? Noch lange kein Grund für ein allumfassendes generelles include. Jedes include File inkludiert seinerseits genau die include Files die es zur Deklaration der Klasse benötigt. Nicht mehr und nicht weniger. Wann immer es geht, verzichtet sie auf das include und arbeitet mit einer forward Deklaration, wenn einem Objekt ein Verweis auf ein anderes Objekt in Form eines Pointers genügt. Und dann greift dann auch wieder der make Mechanismus, der nur die Dinge nachkompiliert, die bei Änderung eines Header Files auch tatsächlich nachkompiliert werden müssen. In meinem vorhergehenden Projekt, bei dem das sauber durchgezogen wurde, war das immerhin ein Unterschied von 1 Stunde Compilierzeit (bei alles compilieren) zu ein paar Sekunden/Minuten die im Regelfall bei einer Änderung angefallen sind.
Niklas Gürtler schrieb: >> Das Problem ist, dass wenn eine einzelne Klasse mit hundert anderen >> zusammenhängt, dann stimmt schon im Aufbau was nicht :-) > Die MainWindow Klasse braucht die Viewer Klasse, die braucht die > Document Klasse, die braucht die 25 verschiedenen Daten-Objekt-Klassen, > die brauchen ihre Basisklassen, Verbindungs-Klassen mit Parametern in > Bezug zu den übergeordneten, etc. ... Und natürlich alles wieder in > Rückrichtung... Dann erzähl mir mal wie du da was trennen willst :D Durch definierte APIs? Softwaredesign?
> Noch lange kein Grund für ein allumfassendes generelles include. > Jedes include File inkludiert seinerseits genau die include Files die es > zur Deklaration der Klasse benötigt. Nicht mehr und nicht weniger. Wann > immer es geht, verzichtet sie auf das include und arbeitet mit einer > forward Deklaration, wenn einem Objekt ein Verweis auf ein anderes > Objekt in Form eines Pointer genügt. Also void* Pointer für alle? Na wenn du 25 includes im dateikopf schön findest, meinetwegen... > Durch definierte APIs? Softwaredesign? Und wo definiert man das API? Nicht zufällig im Header?
Niklas Gürtler schrieb: >> Noch lange kein Grund für ein allumfassendes generelles include. >> Jedes include File inkludiert seinerseits genau die include Files die es >> zur Deklaration der Klasse benötigt. Nicht mehr und nicht weniger. Wann >> immer es geht, verzichtet sie auf das include und arbeitet mit einer >> forward Deklaration, wenn einem Objekt ein Verweis auf ein anderes >> Objekt in Form eines Pointer genügt. > Also void* Pointer für alle? Na wenn du 25 includes im dateikopf schön > findest, meinetwegen... Ähm. Wer hat etwas von void Pointern gesagt? Forward Deklaration != void Pointer!
> Wer hat etwas von void Pointern gesagt? > Forward Deklaration != void Pointer! Okay, aber dann musst du die Klamotten doch #include'n, aber dann wohl in der .c[c] Datei. Ist auch nicht zwangsläufig sonderlich übersichtlich
Niklas Gürtler schrieb: >> Wer hat etwas von void Pointern gesagt? >> Forward Deklaration != void Pointer! > Okay, aber dann musst du die Klamotten doch #include'n, Kann es sein, dass dir der Begriff "forward deklaration" nichts sagt?
1 | #ifndef UNDO_MANAGER_H
|
2 | #define UNDO_MANAGER_H
|
3 | |
4 | #include "ListTemplate.h" |
5 | |
6 | class Command; |
7 | |
8 | class UndoManager |
9 | {
|
10 | public:
|
11 | |
12 | void Add( const Command* cmd) { m_Commands.Append( cmd ); } |
13 | |
14 | private:
|
15 | List< const Command* > m_Commands; |
16 | };
|
17 | #endif
|
ist völlig in Ordnung. Um einen Command Pointer in eine Liste einzufügen, muss der Compiler nicht wissen, wie ein Command Objekt konkret aussieht. Er muss nur wissen, dass es eine derartige Klasse gibt. Die Forward Deklaration verrät ihm dieses. Ob und aus welchen Memberobjekten ein Command Objekt besteht und welche Memberfunktionen es exportiert, ist hier (im Headerfile) ebenfalls uninteressant. Aber für das Listentemplate gilt das nicht. Es wird hier nicht einfach nur so 'von der weiten verwendet'. Der Manager arbeitet aktiv damit. Daher muss hier das entsprechende Include File inkludiert werden.
Ich weiß, was das ist. Gut, wenn du Objekte hast, die andere Objekte nur speichern, und das nur per Pointer, aber nichts damit machen... Ist mir noch nicht so oft passiert. Wenn dein Programm das so oft verwendet...
Niklas Gürtler schrieb: > Ich weiß, was das ist. Gut, wenn du Objekte hast, die andere Objekte nur > speichern, und das nur per Pointer, aber nichts damit machen... Ist mir > noch nicht so oft passiert. Wenn dein Programm das so oft verwendet... Och die arbeiten schon damit. Aber im CPP File. Nicht im Header. Im CPP File steht dann klarerweise der include, der es ermöglicht, die Objekte dann auch soweit zu benutzen. Header ******
1 | #ifndef UNDO_MANAGER_H
|
2 | #define UNDO_MANAGER_H
|
3 | |
4 | #include "ListTemplate.h" |
5 | |
6 | class Command; |
7 | |
8 | class UndoManager |
9 | {
|
10 | public:
|
11 | |
12 | void Add( const Command* cmd) { m_Commands.Append( cmd ); } |
13 | |
14 | void performUndo(); |
15 | |
16 | private:
|
17 | List< const Command* > m_Commands; |
18 | };
|
19 | #endif
|
CPP ***
1 | #include "UndoManager.h" |
2 | #include "Command.h" |
3 | |
4 | void UndoManager::performUndo() |
5 | {
|
6 | .....
|
7 | m_Commands.tail()->doUndo(); |
8 | m_Commands.dropTail(); |
9 | ...
|
10 | }
|
AUch hier wieder: kein Problem. WEnn ein Command Objekt seinerseits aus irgendwelchen Subobjekten besteht, dann obliegt es dem Command.h diese zu inkludieren. Hier im UndoManager geht mich das nichts an. Ich inkludiere einfach Command.h und fertig. Aber der springende Punkt ist: Wer sich die Undomanager.h inkludiert, zieht sich damit nicht automatisch einen Rattenschwanz an anderen inklude ins Boot rein. Mache ich eine Änderung in Command.h, dann wird zwar UndoManager.cpp neu kompiliert, aber es gibt keinen Grund all die Source Files neu zu kompilieren, die lediglich UndoManager.h und nicht Command.h inkludiert haben.
Ich hatte schon verstanden, was du meinst, ein bisschen kann ich das auch... Mir war es immer zu umständlich ständig #include's hinzuzufügen, zentral ging es einfacher, und das bisschen compilezeit hat mir auch nicht wehgetan. Forward declarations brauchte ich dann auch nur sporadisch, wenn zirkuläre Abhängigkeiten auftraten. Ich meine, was schadet es, wenn in einer File, ob Source oder Header, einfach alles sichtbar ist? Edit: Aha, doch compilezeit. Na, wenn sich die Optimierung mal lohnt...
Niklas Gürtler schrieb: > Ich hatte schon verstanden, was du meinst, ein bisschen kann ich das > auch... > Mir war es immer zu umständlich ständig #include's hinzuzufügen, zentral > ging es einfacher, und das bisschen compilezeit hat mir auch nicht > wehgetan. Forward declarations brauchte ich dann auch nur sporadisch, > wenn zirkuläre Abhängigkeiten auftraten. Ich meine, was schadet es, wenn > in einer File, ob Source oder Header, einfach alles sichtbar ist? Du hast nicht zugehört: Weil ein Komplettcompile (denn auf den läuft es hinaus) bei konkreten Projekten schon mal eine Wartezeit von über 1 Stunde benötigen, während das Compilieren des tatsächlich Notwendigen eine Sache von Sekunden/Minuten ist. (Unser aktuelles Projekt wird jede Nacht komplett durchkompiliert. Das dauert zur Zeit runde je 3 Stunden auf 2 Compile-Servern) Daher kann man es auch gleich richtig machen anstatt den vermeintlich leichten Weg zu gehen, zumal das includieren von 5 anderen Header Files in einem H-File (bei kleinen Projekten) nun wirklich nicht die große Sache ist.
Nagut, wenn man bei Compilezeit in Stunden angekommen ist, bringt das dann wohl doch etwas... Aber warum werden solche Monsterprojekte in C/C++ geschrieben? Ist das so zeitkritisch?
Für den Hobbypfuscher mag es ok sein, wenn das Kompilieren länger dauert. Wer aber ernsthaft das Programmieren lernen möchte sollte sich soetwas nicht angewöhnen.
Niklas Gürtler schrieb: > Ich hatte schon verstanden, was du meinst, ein bisschen kann ich das > auch... > Mir war es immer zu umständlich ständig #include's hinzuzufügen, zentral > ging es einfacher, und das bisschen compilezeit hat mir auch nicht > wehgetan. Forward declarations brauchte ich dann auch nur sporadisch, > wenn zirkuläre Abhängigkeiten auftraten. Ich meine, was schadet es, wenn > in einer File, ob Source oder Header, einfach alles sichtbar ist? Man merkt deutlich, dass du noch nie an etwas größeren Projekten (100kloc aufwärts) ernsthaft gearbeitet hast. Glaube es einfach, dass "alles #includen" ganz einfach eine schlechte Idee ist. Das mag bei deinem 10kloc Projekt noch funktionieren. Darüber aber nur noch mit Schmerzen. > Edit: Aha, doch compilezeit. Na, wenn sich die Optimierung mal lohnt... Natürlich lohnt sich das. Gerade bei C++. Edit meint: http://xkcd.com/303/
Niklas Gürtler schrieb: > Nagut, wenn man bei Compilezeit in Stunden angekommen ist, bringt das > dann wohl doch etwas... Aber warum werden solche Monsterprojekte in > C/C++ geschrieben? Ist das so zeitkritisch? Weil man sie in C++ handhaben kann! Genau das ist ja der Sinn der objektorientierten Programmierung: Das Projekt in Klassen aufzuteilen, die möglichst nicht über dubiose Wege miteinander verflochten sind, sondern die Funktionalität in Hierarchieform in Hotspots (eben den Klassen) konzentriert. Das geht natürlich auch in C oder jedem anderen Programmierparadigma. Aber die objektorientierte Programmierung gibt mir Mittel in die Hand, mit der ich nicht auf die freiwillige Disziplin der Teammitglieder angewiesen bin, sondern die Dinge einfordern kann und verhindern kann, dass jeder wie wild in Strukturen rummacht.
> Man merkt deutlich, dass du noch nie an etwas größeren Projekten > (100kloc aufwärts) ernsthaft gearbeitet hast. Entschuldigung, das gab es bei mir an der Schule und bei meiner Zivistelle nicht. Dafür kann ich jetzt Effi Briest interpretieren und Küche putzen. > Glaube es einfach, dass "alles #includen" ganz einfach eine schlechte > Idee ist. Bei solchen Größen kann man dann wohl vermutlich doch in Einheiten trennen, wo ich dann wohl auch wieder kleinere "zentral-includes" nehmen würde... > Für den Hobbypfuscher mag es ok sein, wenn das Kompilieren länger > dauert. > Wer aber ernsthaft das Programmieren lernen möchte sollte sich soetwas > nicht angewöhnen. Ich dachte, bei den Profi's sind Gehirnzyklen teurer als Maschinenzyklen... > Weil man sie in C++ handhaben kann! Geht das nicht z.B. in Scala besser? Da hat man das #include Problem dann eh nicht, aber dafür Closures und die Möglichkeit, sehr hübsche API's zu schreiben. Und potentiell weniger LoC's...
Niklas Gürtler schrieb: > Ich dachte, bei den Profi's sind Gehirnzyklen teurer als > Maschinenzyklen... SInd sie auch. Und genau deswegen macht man keine Rundumschlagincludes. Denn den #include hinschreiben kostet mich 5 Sekunden. Danach kann ich mich wieder um die Algorithmen und was es sonst noch so zu tun gibt kümmern. Du siehst hier ein 'Zeitproblem' wo keines ist. >> Weil man sie in C++ handhaben kann! > Geht das nicht z.B. in Scala besser? Da hat man das #include Problem > dann eh nicht. Du vergisst eines: Du musst auch Leute bekommen, die eine Programmiersprache beherrschen. Es gibt schon genügend Leute da draussen, die glauben nur weil sie in Excel eine Summenformel eingeben können, wären sie Programmierer. Heutzutage musst du schon froh sein, wenn du von den Unis und Fachhochschulen Leute bekommst, die wenigstens die allerwichtigsten Grundlagen von Java, C#, C oder C++ einigermassen sicher beherrschen und mit einem 2000 Zeilen Programm nicht restlos überfordert sind.
> Du musst auch Leute bekommen, die eine Programmiersprache beherrschen. > Es gibt schon genügend Leute da draussen, die glauben nur weil sie in > Excel eine Summenformel eingeben können, wären sie Programmierer. AHA, der Fachkräftemangel :) Ich dachte immer, "gute" Leute hätten Spass daran was neues zu lernen... Naja, kann man gelten lassen. Persönlich hätt ich bei solchen Monstern wenig Spass an C/C++...
Faulheit ist immer ein schlechter Ratgeber bei der Softwareentwicklung. Und Erfahrungsmangel mit Dickkoepfigkeit zu ersetzen, ein noch viel schlechterer.
Niklas Gürtler schrieb: > Ich dachte immer, "gute" Leute hätten Spass > daran was neues zu lernen... :-) Eine Programmiersprache ist nur ein Werkzeug. Mehr nicht. Aber auch dieses Werkzeug will beherrscht werden. Das ändert aber nichts daran, das mann wie in meinem Fall fit in 3D Geometrie sein muss. Die Programmiersprache ist nur ein Mittel zum Zweck um damit im Raum zb Vektorgeometrie zu betrieben oder zu bestimmen, wie hoch ein Türblatt sein muss, wenn der Türstock im Estrich steht, du diesen und jenen Bodenaufbau hast und eine Stahlzarge mit Holzverkleidung vorhanden ist. Oder zu berechnen, wie die Sparren eiens Dachstuhls verschnitten sein müssen, wenn die Grundrissgeometrie gegeben ist und bestimmte Firsthöhen bei unterschiedlichen Dachformen in einem verwinkelten Gebäude mit 6 Dachfenstern und 5 Gauben angestrebt werden und diese Zahlen dann auch stimmen müssen, weil der Zimmermann danach den Holzpreis bestimmt. Da geht die Entwicklungszeit drauf. Nicht bei einem fehlenden include.
> Das ändert aber nichts daran, das mann wie in meinem Fall fit in 3D Geometrie sein muss. Na hoffentlich in der Schule aufgepasst :-D > Da geht die Entwicklungszeit drauf. Nicht bei einem fehlenden include. Das ist mir auch irgendwann mal aufgegangen. Mit diesem Thread habe ich jetzt vermutlich mehr Zeit verschwendet als durchs Hinzufügen von #include's... mist.
Niklas Gürtler schrieb: > Die MainWindow Klasse braucht die Viewer Klasse, die braucht die > Document Klasse, die braucht die 25 verschiedenen Daten-Objekt-Klassen, > die brauchen ihre Basisklassen, Verbindungs-Klassen mit Parametern in > Bezug zu den übergeordneten, etc. ... Und natürlich alles wieder in > Rückrichtung... Hallo Niklas, ich hab den ziemlich bestimmten Eindruck, daß dein ganzes Projekt nicht wirklich gut durchdacht angelegt ist. Jede Implementierung von egal was, die zu zirkulären Referenzen führt, ist im tiefsten Grund falsch angelegt. Beispiel: Wozu die Rückrichtung? Wozu braucht eine richtig konstruierte Daten-Objekt-Klasse eine (bestimmte!?) Document-Klasse? Ein Objekt (um's mal pascalisch zu sagen) soll ja gerade seine Innereien gegen die Außenwelt abschotten und sich selbst genügen. Deshalb braucht es keine Kenntnis der Gefilde, in denen es benutzt wird und demzufolge auch keine Definitionen, die aus der Welt der Anwender dieses Objektes kommen. Wenn dein Problem sich ohne solche Definitionen nicht darstellen läßt, dann ist der objektorientierte Ansatz genau DAFÜR nicht der Richtige. Ich hab sowieso den Eindruck, daß viele Leute es mit dem ganzen Klassenkram viel zuweit treiben und alles in irgendwelche Klassenhierarchien pressen wollen - auch oder gerade dann, wenn das zu bewältigende Problem eigentlich eine ganz andere Herangehensweise erfordern würde. W.S.
Nun, ich habe z.B. eine "Network" Klasse, die im Prinzip einen Graphen mit "Extra's" repräsentiert, diese hat eine Liste mit Pointern auf "Object"s (Knoten und Kanten). Diese Objekte haben aber wiederum einen Pointer auf die zugehörige Network-Instanz, um andere Objekte zu finden und anzulegen, globale Eigenschaften des Graphen abzufragen etc. Das ist dann die Aufwärtsreferenz. Ich bin gespannt, wie es ohne gehen soll.
Es geht SO natürlich garnicht, denn die Sache ist von Grund auf völlig
verkehrt angelegt. Dessen bin ich mir bei deiner Antwort jetzt sicher.
>Diese Objekte haben aber wiederum einen
Pointer auf die zugehörige Network-Instanz, um andere Objekte zu finden
und anzulegen, globale Eigenschaften des Graphen abzufragen etc.
Jaja, Objekte, die sich selbständig machen und versuchen, Eigenschaften
ihres (jeweiligen) Benutzers zu besorgen... Oh Mann, genau SOWAS ist
grundfalsch. Wenn sich Eigenschaften eines Benutzers ändern, dann ist es
Obliegenheit dieses Benutzers, dies allen seinen benutzten Objekten zu
verklickern - und nicht umgekehrt.
Ich befürchte, daß du über kurz oder lang in deinen eigenen
Verstrickungen (runter und reverse) dich hoffnungslos verhakelt haben
wirst. Wie gesagt, es könnte einfach nur persönliche Dämlichkeit im
Grundentwurf sein, es könnte aber auch einfach nur sein, daß
Objektorientiertheit für DIESES Projekt ungeeignet ist. Was besser dafür
ist, mußt du allerdings selbst herausfinden. Threads mit Botschaften,
ganz ohne Objekte? oder ne Statusmaschine im Hintergrund? wer weiß? Denk
mal wieder.
W.S.
Oha, jetzt werden wir ja mal richtig freundlich hier. Du meinst also, jedes Objekt soll einen Cache der Eigenschaft haben, und das Network-Objekt soll beim Ändern dieser jedem Objekt die neuen Daten geben, sodass es sich die im Cache speichert und niemals von "oben" abfragt? Klingt hypereffizient. Vor allem, wenn es viele Objekte, viele Änderungen, aber eigentlich nur wenige Abfragen sind. Genau, dieses Projekt ohne OOP, da müssten mir aber einige C-Features entgangen sein, dass das besser gewesen wäre. Bis jetzt habe ich mich nicht verhakelt, es läuft ziemlich gut & stabil, und ich kann immernoch Änderungen einpflegen. Threads? Für jeden Knoten/Kante einen? Es geht hier um Desktop-PC's, nicht um Rechencluster mit 1000 CPU's. Statusmaschine? Was soll die denn da machen? Eine solche findet sich auch im Projekt, praktisch die GUI, aber bei der Datenverwaltung?
W.S. schrieb: > Es geht SO natürlich garnicht, Muss nicht sein. Aus dem bisher gesagten kann man nicht unbedingt auf eine vernünftige oder unvernünftige Struktur schliessen. Solange die Knoten lediglich einen Rückwärtspointer auf den "Baum an sich" haben, kann das durchaus ok sein. Ob dieses Baum Objekt identisch mit dem Netzwerkobjekt IST bzw. sein soll, oder ob nicht eigentlich ein Netzwerkobjekt einen Baum HAT, kann ich aus der Ferne hier nicht beurteilen. > Ich befürchte, daß du über kurz oder lang in deinen eigenen > Verstrickungen (runter und reverse) dich hoffnungslos verhakelt haben > wirst. Das allerdings ist mir auch tatsächlich in meinem ersten OOP Programm passiert. Auf den ersten Blick hat es gut ausgesehen, wenn in einer Geometrie, bestehend aus Flächen, Kanten und Punkten an den Kanten jedes 'Objekt' (zb Punkte) alle anderen kennt (ein Punkt weiß zu welchen Kanten er gehört). Ich hab allerdings den Fehler gemacht, keine strikte funktionale Vorwärtshierarchie einzuhalten. Punkte haben sich selbsttätig aus Kanten ausgetragen etc. etc. Die Folge war: ein undurchschaubares Geflecht an funktionalen Abhängigkeiten, das nicht mehr zu kontrollieren war. Ich hab meine Lektion gelernt :-)
Karl Heinz Buchegger schrieb: > Ob dieses Baum Objekt identisch mit dem Netzwerkobjekt IST bzw. sein > soll, oder ob nicht eigentlich ein Netzwerkobjekt einen Baum HAT, kann > ich aus der Ferne hier nicht beurteilen. Ein Network-Objekt (also der Graph) hat ein paar Tausend "Object"'s (Knoten+Kanten), und diese haben ihre Aufwärtspointer zum "Network"-Objekt, sowie Pointer zu den benachbarten Kanten bzw. Knoten. Das muss auch so, denn auf die muss schnell zugegriffen werden können. Das ein/aus-tragen macht das "Network" Objekt.
Niklas Gürtler schrieb: > Karl Heinz Buchegger schrieb: >> Ob dieses Baum Objekt identisch mit dem Netzwerkobjekt IST bzw. sein >> soll, oder ob nicht eigentlich ein Netzwerkobjekt einen Baum HAT, kann >> ich aus der Ferne hier nicht beurteilen. > Ein Network-Objekt (also der Graph) hat ein paar Tausend "Object"'s > (Knoten+Kanten), darum gehst nicht. Es geht um die Frage ob ein Network-Objekt selber ein Graph ist oder nicht. Im Sinne von: ein PWK IST EIN Auto aber ein PKW HAT EINEN Motor Je nachdem modelliert man das dann anders. "IST EIN" ist ein Indiz für Ableitung "HAT EIN" ist ein Indiz für Membership class Auto { .... }; class PKW : public Auto { .... Motor m_Motor; }; In diesem Sinne würde ich eventuell den eigentlichen Graphen in eine eigene Graph Klasse kapseln. Und dann stellt sich die Frage class NetObject : public Graph oder class NetObject { Graph m_Nodes; } Je nachdem was dann sinnvoller ist, bzw. wie der Zusammenhang tatsächlich funktioniert. Vielleicht macht das auch gar keinen Sinn. Aber auf jeden Fall wäre es eine Möglichkeit, wie man die Eigenschaft einen Graphen zu bilden vom Netzwerk Objekt komplett trennen kann. Was dann wiederrum bedeutet, dass die Graphfunktionen von einem Netzwerk Objekt gar nichts wissen müssen und daher dann auch den #include für das Netzwerk Objekt gar nicht benötigen :-) (und darum ging es ja ursprünglich) > und diese haben ihre Aufwärtspointer zum > "Network"-Objekt, sowie Pointer zu den benachbarten Kanten bzw. Knoten. > Das muss auch so, denn auf die muss schnell zugegriffen werden können. > Das ein/aus-tragen macht das "Network" Objekt. Das sehe ich soweit als ok an. Wichtig ist IMHO dass du einen kontrollierten Control-Flow hast. Nicht der Knoten selber allokiert in einer Funktion einen neuen Knoten und manipuliert an jeglicher Kontrolle vorbei den Baum, sondern er bittet das Baum Objekt das zu tun. Hab ich so erst mal keinen Einwand. (Was aber nicht viel heißen muss, ich keine deine Problemstellung zu wenig)
> Es geht um die Frage ob ein Network-Objekt selber ein Graph ist oder > nicht. Das Network-Objekt IST der Graph. Es ist sein einziger Zweck, die Knoten+Kanten zu speichern und zu bearbeiten. Wäre diese Funktionalität in einem eigenen Objekt, würde das Network-Objekt ja nur delegieren und wäre somit sinnlos. > Das sehe ich soweit als ok an. Na wenigstens einer. "W.S." meint ja das wär kapitaldämlich. > Wichtig ist IMHO dass du einen kontrollierten Control-Flow hast. Nicht > der Knoten selber allokiert in einer Funktion einen neuen Knoten und > manipuliert an jeglicher Kontrolle vorbei den Baum, sondern er bittet > das Baum Objekt das zu tun. Hab ich so erst mal keinen Einwand. Richtig, und dafür brauche ich den aufwärts-Pointer im Knoten-Objekt auf das Network-Objekt, welches eine create_object -Funktion hat. Also ist die Knoten-Klasse nicht wiederverwendbar weil nicht vom Network-Objekt trennbar. Weil ich damals noch nichts von Graphentheorie wusste, sind die Namen unüblicherweise "Network" und "Object" (aber damit eher konform zur Notation in einem anderen Programm), aber sonst ist es ein Graph mit Knoten+Kanten und ein paar Erweiterungen.
Niklas Gürtler schrieb: >> Es geht um die Frage ob ein Network-Objekt selber ein Graph ist oder >> nicht. > Das Network-Objekt IST der Graph. Es ist sein einziger Zweck, die > Knoten+Kanten zu speichern und zu bearbeiten. Wäre diese Funktionalität > in einem eigenen Objekt, würde das Network-Objekt ja nur delegieren und > wäre somit sinnlos. Siehst du, da kenne ich deine Aufgabenstellung einfach nicht. Für mich hat sich Netzwerk Objekt angehört wie: Das ist ein Gerät welches ein Netzwerk betreut. ie. ein Router oder eine Netzwerkkarte oder ein Modem oder .... also sowas wie ein physikalisches Gerät das an einem Netzwerk (Ethernet, Toekn Ring etc) hängt. >> Das sehe ich soweit als ok an. > Na wenigstens einer. "W.S." meint ja das wär kapitaldämlich. Die Schwierigkeit ist immer, dass ein Klassenaufbau von den konkreten Aufgabenstellungen abhängt. Ins Blaue hinein ist sowas immer schwer zu beurteilen. Was in dem einen Fall dämlich ist, kann an anderer Stelle durchaus Sinn machen. > das Network-Objekt, welches eine create_object -Funktion hat. Also ist > die Knoten-Klasse nicht wiederverwendbar weil nicht vom Network-Objekt > trennbar. Muss nicht sein. Nenn deine Network Klasse 'Graph', gib der einen 'GraphNode' und leite vom GraphNode deinen Network-Objekte her. Die Graph-Manipulations Methoden kannst du ann wunderbar von den Dingen trennen, die ein GraphNode zu einem Network Node machen. Schon hast du eine wiederverwendbare Komponente (Graph + GraphNode), die du an anderer Stelle für Graphen wiederverwenden kannst und die du durch Ableitung für deinen speziellen Fall hier adaptiert hast. Wiederverwendbar heißt ja nicht, dass es sich nur um 1 einzelne Klasse handelt oder handeln muss. Gerade so grundlegende Datenstrukturen wie Listen, Bäume, Tabellen bieten sich meistens dazu an, sie in eigene Einheiten auszulagern und als Building Blocks in einer Basis-Library zur Verfügung zu stellen. Ein 3D Package hat zb auch als Grundkomponenten einen 3D-Vektor und eine Transformationsmatrix. Machen die Vektoren für sich alleine noch Sinn, so macht die Matrix ohne die Vektoren keinen Sinn mehr.
> Siehst du, da kenne ich deine Aufgabenstellung einfach nicht. > Für mich hat sich Netzwerk Objekt angehört wie: > Das ist ein Gerät welches ein Netzwerk betreut. ie. ein Router oder eine > Netzwerkkarte oder ein Modem oder .... also sowas wie ein physikalisches > Gerät das an einem Netzwerk (Ethernet, Toekn Ring etc) hängt. Okay, ungünstig ausgedrückt, es ging um Pipeline's, und in einem anderen Programm heißt die Menge der Pipelines "Netwerk", das hab ich einfach übernommen. > Schon hast du eine wiederverwendbare Komponente (Graph + GraphNode), die > du an anderer Stelle für Graphen wiederverwenden kannst und die du durch > Ableitung für deinen speziellen Fall hier adaptiert hast. Tja, und ein Stückchen Zusatzaufwand gehabt, um einen Teil herauszutrennen... Kann man aber machen, okay, aber noch verschachteltere Klassenstrukturen machen es nicht notwendigerweise übersichtlicher. > Ein 3D Package hat zb auch als Grundkomponenten einen 3D-Vektor und eine > Transformationsmatrix. Machen die Vektoren für sich alleine noch Sinn, > so macht die Matrix ohne die Vektoren keinen Sinn mehr. Wenn du wüßtest, was man mit Matrizen alles machen kann :-) Ok, aber nicht in einem 3D Package ;-)
Niklas Gürtler schrieb: >> Netzwerkkarte oder ein Modem oder .... also sowas wie ein physikalisches >> Gerät das an einem Netzwerk (Ethernet, Toekn Ring etc) hängt. > Okay, ungünstig ausgedrückt, es ging um Pipeline's, und in einem anderen > Programm heißt die Menge der Pipelines "Netwerk", das hab ich einfach > übernommen. :-) Das leidige Problem der Nomenklatur. Damit kämpfen wohl alle. :-) >> Schon hast du eine wiederverwendbare Komponente (Graph + GraphNode), die >> du an anderer Stelle für Graphen wiederverwenden kannst und die du durch >> Ableitung für deinen speziellen Fall hier adaptiert hast. > Tja, und ein Stückchen Zusatzaufwand gehabt, um einen Teil > herauszutrennen... Schon klar. Zum jetzigen Zeitpunkt ist das Arbeit. Aber probiers im nächsten Projekt aus. Du wirst sehen, der Aufwand ist gar nicht so hoch. Und da du dann eine klare Trennlinie hast, in welche Klasse welche Funktionalität gehört, wird das Gesamtsystem meistens sogar einfacher und übersichtlicher. Alles was mit topologischen Manipulationen am Graphen zu tun hat, gehört zur 'Basis'-Graph Funktionalität und in die dortigen Klassen. Alles andere in den Network Node. Ich finde überhaupt: Ein wesentlicher Punkt in der OOP ist es, sich bei jeglicher Funktionalität zu fragen: In welche Klasse gehört sie? Ist die Klasse überhaupt dafür zuständig? Erst dann entfaltet sich die wirkliche Power von OOP. > Kann man aber machen, okay, aber noch > verschachteltere Klassenstrukturen machen es nicht notwendigerweise > übersichtlicher. Verschachtelt? Wo ist da etwas verschachtelt? >> Ein 3D Package hat zb auch als Grundkomponenten einen 3D-Vektor und eine >> Transformationsmatrix. Machen die Vektoren für sich alleine noch Sinn, >> so macht die Matrix ohne die Vektoren keinen Sinn mehr. > Wenn du wüßtest, was man mit Matrizen alles machen kann :-) Ok, aber > nicht in einem 3D Package ;-) LOL Ja, ja. Adjazenzmatrizen sind ein anderes Thema :-)
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.