Grundlegende Fragen über das Design einer Shared Library in C++ mit Java im Hinterkopf
-
Nathan schrieb:
Ist das zu viel verlangt, einfach die Konstruktoren zur korrekten Erzeugung zu nutzen und via Memberfunktionen die Daten wegzukapseln?
Ganz ohne Factory. Und ohne PIMPL, dessen Sinn und Zweck es eigentlich ist, nicht alle bneötigten Header zu inkludieren.
Und RAII hat keine Nachteile, die den "Stack sprengen könnten".Nathan, mir bringt es leider nicht viel, wenn Du sagst: Mach es so, so und so, lass dies und jenes weg. Was mir wichtig ist:
- Warum so und nicht anderst ? Was ist sind deine Beweggründe, dies so zu realiseren ?
- Betreffend Header und exportieren einzelner Funktionen: Der Verwender hat ja dann doch den Header der Implementierung (Was ich nicht will). Wenn ich was falsch verstehe, mach doch bitte schnell ein Codebeispiel
- (Off Topic): Was passiert wenn ich mehrere grosse Listen auf den Stack werfe. Der Stack hat doch eine vom System vorgeschrieben [Vor dem Start änderbare] Grösse ?Gruss Simon
-
swaechter schrieb:
Nathan schrieb:
Ist das zu viel verlangt, einfach die Konstruktoren zur korrekten Erzeugung zu nutzen und via Memberfunktionen die Daten wegzukapseln?
Ganz ohne Factory. Und ohne PIMPL, dessen Sinn und Zweck es eigentlich ist, nicht alle bneötigten Header zu inkludieren.
Und RAII hat keine Nachteile, die den "Stack sprengen könnten".- Warum so und nicht anderst ? Was ist sind deine Beweggründe, dies so zu realiseren ?
Man spart sich Programmierarbeit, wenn man nur Klassen ohne Factory hat.
- Betreffend Header und exportieren einzelner Funktionen: Der Verwender hat ja dann doch den Header der Implementierung (Was ich nicht will). Wenn ich was falsch verstehe, mach doch bitte schnell ein Codebeispiel
Es wäre einfacher wenn du sagen würdest, was deine Klassen machen.
Nur mit dem jetzigen Beispiel lässt sich nur schwer Code zeigen.
Und ja, wenn du private Daten von einem Typ hast, die keine Pointer sind, muss der Header inkludiert werden. Aus rein programmiertechnischer Sicht ist das kein Problem, da der Client eh nicht (diverse Tricks jetzt mal außen vor gelassen) darauf zugreifen kann. Höchstens aus Wartungsgründen, da er dann recompilen muss, wenn sich die Implementierung ändert. Und deswegen gibt es PIMPL.- (Off Topic): Was passiert wenn ich mehrere grosse Listen auf den Stack werfe. Der Stack hat doch eine vom System vorgeschrieben [Vor dem Start änderbare] Grösse ?
RAII ist nicht "schmeiß alles auf dem Stack", RAII ist "lass Destruktoren aufräumen". Aber solange es keinen Grund gibt, etwas auf dem Heap zu werfen, lass es auf dem Stack. Der ist groß genug.
-
Nathan schrieb:
Es wäre einfacher wenn du sagen würdest, was deine Klassen machen.
Was ich im Kopf habe (Als Klassen):
- Platform: Kann Projekte erstellen/editieren/löschen und den Projekten Einträge hinzufügen/editieren/entfernen
- Project: Beinhaltet Name und Einträge als Liste
- Entry: Beinhaltet einen (Key)Namen und BeschreibungNun, Gedanken betreffend der Platform:
- Die Platform übernimmt das Inputchecking
- Der Verwender muss die Platform verwenden, um Projekte und Einträge ändern zu können
- Project und Entry sollen als reine Datenobjekte dienen, sprich keine Logik enthalten (Darum auch die Bearbeitung über die Platform)Wenn Du mir einen anderen Weg aufzeigen kannst, sowas zu realiseren, her damit

-
Also in etwa so?
struct project {...}; struct entry {...}; class platform { public: add_or_change_entry(size_t index, entry e); add_or_change_project(size_t index, project p); private: container projects_; container entrys_; };Was spricht dagegen, dass genau so zu machen?
Edit: Mit entry in project, hatte das überlesen.
-
Genau, etwas in der Art. Nun möchte ich aber die Implementierung von der Schnittstelle abtrennen, um zum Beispiel beim Hinzufügen/Änderung von privaten Attributen in der Implementierung nicht gleich das ganze Offset zu verschieben (Bin auf dem Gebiet neu & daran mich einzulesen).
Nun war meine Idee:
- Öffentliche Klasse besitzt einen Pointer zu der libraryinternen Implementierung
- Methodenzugriffe auf die öffentliche Klasse werden an die Implementierung weitergeleitet, diese arbeitet die Sache ab
- Änderungen in der Implementuerung machen eine Neukompilierung der Applikation mit der (neuen) Library überflüssig ?Oder habe ich da was falsch verstanden ? An dieser Stelle könnte ich doch das PIMPL Pinzip verwenden ?
Gruss Simon
-
swaechter schrieb:
Nun möchte ich aber die Implementierung von der Schnittstelle abtrennen, um zum Beispiel beim Hinzufügen/Änderung von privaten Attributen in der Implementierung nicht gleich das ganze Offset zu verschieben (Bin auf dem Gebiet neu & daran mich einzulesen).
Wozu? Wo liegt das Problem, wenn sich das Layout der Membervariablen ändert?
Was du beschreibst, ist gerade das PImpl-Idiom. Wie erwähnt wird es primär zur Verringerung von Compilezeit-Abhängigkeiten eingesetzt.
-
Nexus schrieb:
Wozu? Wo liegt das Problem, wenn sich das Layout der Membervariablen ändert?
Klar könnte ich darauf verzichten und immer davon ausgehen, dass Applikation und Library neu kompiliert & gelinkt werden, aber eventuell möchte ich das eines Tages ändern und dann alles umbauen, nur um das zu erreichen ? Lieber fange ich dann gleich richtig an und setze auf eine lose Kopplung zwischen Implementierung und Schnittstelle
Betreffend Businessobjekten: Gibt es da auch eine gute Möglichkeit, eine lose Kopplung zu erreichen oder schreibt man einfach eine Klasse, bestehend aus lauter virtuellen Funktionen und das eigentliche (interne) Businessobjekt erbt davon ? PIMPL würde in einem solchen Fall ja einen enormen Overhead generieren (Was mehr auffällt als bei einer Klasse, die aus lauter Logik besteht und Inpputchecking betreibt) ?
-
swaechter schrieb:
Klar könnte ich darauf verzichten und immer davon ausgehen, dass Applikation und Library neu kompiliert & gelinkt werden, aber eventuell möchte ich das eines Tages ändern und dann alles umbauen, nur um das zu erreichen ? Lieber fange ich dann gleich richtig an und setze auf eine lose Kopplung zwischen Implementierung und Schnittstelle
Nachträglich pimpl einfügen? Das ist quasi umsonst, da muss ja nicht einmal die Schnittstelle geändert werden.
PIMPL würde in einem solchen Fall ja einen enormen Overhead generieren
lol
-
justlol schrieb:
swaechter schrieb:
Klar könnte ich darauf verzichten und immer davon ausgehen, dass Applikation und Library neu kompiliert & gelinkt werden, aber eventuell möchte ich das eines Tages ändern und dann alles umbauen, nur um das zu erreichen ? Lieber fange ich dann gleich richtig an und setze auf eine lose Kopplung zwischen Implementierung und Schnittstelle
Nachträglich pimpl einfügen? Das ist quasi umsonst, da muss ja nicht einmal die Schnittstelle geändert werden.
PIMPL würde in einem solchen Fall ja einen enormen Overhead generieren
lol
Danke für deinen sehr konstruktiven Beitrag. Wenn Du schon solchen Content schreibst, registrier dich doch bitte, dass man dich erkennen kann (und ich dir konkret aus dem Weg gehen kann)
Betreffend PIMPL nachträglich einfügen:
1.) Was muss nicht mehr eingefügt werden ? Sollte es wie in Frage 2 sein hast Du definitiv kein PIMPL...
2.) Nein sicher nicht. Hast Du eine Klasse, in welcher gewisse Methoden exportiert und andere nicht exportiert werden, verschiebt sich dein Offset immer, wenn Du gewisse Bestandteile änderst (Methoden/Attribute etc). Das ist nicht gerade das, was ich anstrebe (Betreffend loser Kopplung).Vorherige Frage steht immer noch im Raum
-
Binärschnittstellen in C++ sind sowieso eine Angelegenheit für sich. Du bist wegen fehlendem standardisierten ABI relativ eingeschränkt und oft zum Neukompilieren gezwungen (spätestens wenn du den Compiler oder nur schon die Version oder bestimmte Flags wechselst).
Du solltest dir also überlegen, wie weit du deswegen dein Design verkomplizieren willst.
-
Hallo Nexus
Danke für deinen Antwort. Ich muss ein paar Sachen klarstellen und ergänzen, da ich erst seit gestern in das Thema reingekommen bin und sich der Thread doch recht entwickelt hat. Früher habe ich Libraries einfach exportiert und das wars - mehr nicht (Schnittstelle und Implementierung = Eine Klasse). Seit gestern habe ich mich vertieft damit auseinandergesetzt, um das ganze sauberer gestalten zu können. Zu ergänzen (meinerseits) ist:
Infrastruktur
- Ich verwende für die Applikation und die Library denselben Compiler und dasselbe CRT (Sprich MSVC und dessen CRT oder GCC/Clang und dessen GLIBC)
- Es kann vorkommen, dass die Applikation wegen Änderungen in der Library nicht neukompiliert wird (Wir reden über eine Patch Level Änderung in der Library, nicht Major & Minor, deshalb muss gewährleistet sein, dass sich das Offset nicht ändert (Z.B neue Variable wird eingeführt). ANMERKUNG: Ich habe mich noch nicht vertieft in Offset Changing eingelesen. Wird das ganze über eine lose Kopplung realisiert, funktioniert die Applikation auch mit der geupdateter Library. Verwende ich aber 1 Klasse wie bereits mehrmals in diesem Thread vorgeschlagen wurde, ist es (gut) möglich, dass sich das Offset der Library ändert [und das der Applikation nicht, da kein recompile].Das CRT zwischen Applikation und Library bleibt aber auch in diesem Fall gleich (Build Server)Betreffend deiner Aussage 1:
- Wird deine Aussage nicht eher unwichtig/rückt in den Hintergrund, wenn ich garantieren kann, dass ich denselben Compiler und dasselbe CRT verwenden kann. Es erfolgt definitv kein Compiler/CRT Mixing ! Entweder kompiliert das Buildsystem ALLES oder nur Änderungen, aber immer mit dem gleichen Compiler/CRT.Betreffend deiner Aussage 2:
Nexus schrieb:
Du solltest dir also überlegen, wie weit du deswegen dein Design verkomplizieren willst.
Wie darf man das verstehen. Könntest Du mir ein komplettes Codebeispiel geben, wie Du es machen würdest, wenn dir jemand den Auftrag gibt, eine Klasse zu schreiben, welche Projekte und Eintrage machen/editieren/löschen kann, WENN Du davon ausgehen kannst, dass der Compiler und das CRT IMMER dasselbe sind und eine lose Kopplung von Nöten ist ? Ich wär dir dafür sehr dankbar.
Schöne Weihnachten dir & Mitleser
Gruss Simon
-
Bezüglich ABI kompatiblen Änderungen in Klassen:
AFAIK ändert sich die ABI Schnittstelle einer Klasse nicht, wenn ein neuer Member(eine neue Memeber-Methode?) immer ans ende der Klassen definition gepackt wird.
Wobei ich nicht weis, ob sich die ABI eventuell ändert, wenn eine Änderung in einer Klasse gemacht wird, welche als Basis für andere Klassen Verwendung findet
-
firefly schrieb:
Bezüglich ABI kompatiblen Änderungen in Klassen:
AFAIK ändert sich die ABI Schnittstelle einer Klasse nicht, wenn ein neuer Member(eine neue Memeber-Methode?) immer ans ende der Klassen definition gepackt wird.
Bei nicht-statischen Datenmembern wird die Klasse dadurch bei allen Systemen die ich kenne inkompatibel, da sich
sizeof(T)ändert.Neue Methoden sind dagegen meist egal. So lange sie nicht virtuell sind, ist dabei auch egal wo man sie dazuschreibt. Und wenn sie virtuell sind, man sie aber brav hinter alle anderen virtuellen Funktionen schreibt, dann ist es auch egal. Zumindest eben auf den Systemen nicht ich kenne.
Hat aber mit Standard C++ nix zu tun, da der Standard diesbezüglich nix vorschreibt.
Wobei ich nicht weis, ob sich die ABI eventuell ändert, wenn eine Änderung in einer Klasse gemacht wird, welche als Basis für andere Klassen Verwendung findet
Wenn nicht-statische Datenmenber betroffen sind, ist die Chance gross, dass alle abgeleiteten Klassen neu übersetzt werden müssen. Wenn sich die Grösse oder das Layout der Klasse bzw. abgeleiteten Klassen dadurch ändert auch der gesamte Client Code.
Sowas sind aber Dinge über die ich mir üblicherweise nicht den Kopf zerbreche.
Für mich gibt es ein paar sinnvolle Varianten damit umzugehen:
* Reines C Interface
* COM
* PIMPL
* volles C++ Interface + immer alles neu ÜbersetzenZwischenlösungen mache ich da nur wenn ich wirklich muss. Was in den letzten Jahren nicht vorgekommen ist. Ich hab' aber auch nur mehr ein Projekt wo ich ein stabiles binary Inteface garantieren muss, und das verwendet COM.