Forum: Compiler & IDEs Definition von Strukturen


von Thomas F. (thomas-hn) Benutzerseite


Lesenswert?

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

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

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

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von der mechatroniker (Gast)


Lesenswert?

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.

von willibald (Gast)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Michael B. (mb_)


Lesenswert?

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?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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!

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

> 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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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?

von berufspfuscher (Gast)


Lesenswert?

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.

von Michael B. (mb_)


Lesenswert?

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/

von Karl H. (kbuchegg)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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

von P. S. (Gast)


Lesenswert?

Faulheit ist immer ein schlechter Ratgeber bei der Softwareentwicklung. 
Und Erfahrungsmangel mit Dickkoepfigkeit zu ersetzen, ein noch viel 
schlechterer.

von Karl H. (kbuchegg)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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

von W.S. (Gast)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von W.S. (Gast)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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?

von Karl H. (kbuchegg)


Lesenswert?

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

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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)

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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
Noch kein Account? Hier anmelden.