Forward Declaration "manuell" oder #include?

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Forward Declaration "manuell" oder #include?

Beitrag von starcow »

Guten Nachmittag zusammen :-)
Folgende Frage brennt mir schon länger untern den Nägeln C(++):

Wenn ich den konkreten Aufbau eines Structs nicht zu kennen brauche, weil ich blos mit Pointern hantiere, kann es dann sinnvoll sein, "manuell" eine Forward Declaration jener Struktur vorzunehmen, anstatt den entsprechenden Header zu includieren?
Mit dem Verzicht auf das include würde sich ja der Umfang der Translation Unit möglicherweise substantiell verringern, oder?

Eine weitere Frage in diesem Kontext:
Ich bin mir immer unsicher, ob ich einen Header selbst dann nochmals explizit includieren sollte, wenn ich genau weiss, dass dieser bereits indirekt über einen anderen Header includiert wurde.
Für den Compiler spielts ja offensichtlich keine Rolle (include guards) - doch ich könnte mir denken, dass das eine oder andere vielleicht als die bessere Praxis gilt, da es der "Transparenz" und der Lesbarkeit des Code dient?

Gruss, starcow
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von Krishty »

seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Matthias Gubisch
Establishment
Beiträge: 470
Registriert: 01.03.2009, 19:09

Re: Forward Declaration "manuell" oder #include?

Beitrag von Matthias Gubisch »

Mal ab von Modules weil der Compilersupport da leider doch noch relativ rudimentaer ist, oder falls du sie einfach nicht nutzen willst...

Um die compile Zeiten in den Griff zu bekommen bieten sich forward declarations an, vor allem in Headerfiles.

Persönlich fahre ich mit dem Ansatz "include what you use" ganz gut. Das bedeutet dass man die Header für die benutzten Typen explizit einbindet und sich nicht auf transitive Abhängigkeiten verlässt.
Wenn man ausserdem konsequent mit forward declarations arbeitet erledigt sich das Thema an vielen Stellen von selber.
Bevor man den Kopf schüttelt, sollte man sich vergewissern einen zu haben
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von Schrompf »

Ich mach das auch so ähnlich. In Headern soviel wie möglich Vorwärtsdeklarationen, in der CPP füge ich Includes hinzu, bis die roten Strichellinien alle weg sind. So viel wie möglich dort in anonymen Namespaces, aber der Faulheit wegen implementier ich auch gern mal die ganze Klasse im Header.

Die wirklich wichtigen Moves sind eigentlich, die fetten Standardheader und <windows.h> aus den Headern rauszuhalten. Alles andere ist Kleinkram
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Jonathan
Establishment
Beiträge: 2352
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von Jonathan »

https://include-what-you-use.org/

Ich hab das mal vor ein paar Jahren benutzt. Damals war das Problem, das es recht umständlich aufzusetzen war, auch weil mein Code damals ein paar Stellen enthielt, die clang nicht gefielen. Ich bin mir nicht sicher, ob Support dafür heute so viel besser ist, vielleicht dauert es immer noch einen Tag, das ganze ans Laufen zu kriegen.

Aber - wenn man schon ein Projekt mit 100 Dateien hat, dann will man einfach nicht alle Includes von Hand optimieren. Ich habe es lange nicht mehr benutzt, weil es mir nicht wirklich wichtig genug war, aber mittlerweile sollte ich vermutlich mal wieder. Wenn es einmal läuft waren quasi alle Vorschläge gut, nur an wenigen Stellen habe ich explizit von den normalen Regeln abgewichen, und ansonsten damit tüchtig aufgeräumt.

Lies dir zumindest durch, was die Philosophie dahinter ist, die Regeln sind dort alle erklärt und begründet. Vermutlich findest du sie sinnvoll und willst dich an sie halten, ob du dann dieses Tool zum automatisieren benutzen willst, ist dann nochmal eine andere Frage.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von Krishty »

Hat niemand eine Meinung dazu, dass man für diese Include-Optimierungs-Müll ständig zwischen Code-Duplikation und Performance abwägen muss?

Das ist für mich nämlich das eigentlich interessante Problem. Wenn C-#includes und C++-Klassen nicht by-design FUBAR wären (sie fordern grundsätzlich exponentielles Wachstum), wäre die Performance in Ordnung. Dann käme niemand auf die Idee, Deklarationen – die ja oft selber bereits das erste Duplikat einer Definition in namensähnlicher CPP sind – in leicht anderer Form nochmals zigfach über die Codebase zu verstreuen.

Nun stehen wir vor der Frage, wo wir die Grenze ziehen. Ein Include weglassen können, und dafür eine Template-Deklaration in 20 Dateien duplizieren? Schrompf zieht sie nach Performance. Welche eigentlich? Mal gemessen?

So enttäuscht ich von Modules bin, so nötig erachte ich sie. Vor allem für Neulinge. Solche Überlegungen sollte 2023 niemand mehr anstellen müssen. Das ist echt verschwendete Lebenszeit, seit wir eine Alternative haben.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von dot »

FWIW, Modules ändern nichts an der Tatsache, dass man in C++ Dinge deklarieren muss, bevor man sich auf sie beziehen kann. Modules bringen uns effektiv nur einen neuen Weg, Deklarationen zu importieren. Wenn du dein Module beispielsweise in separate Interface- und Implementation-units aufteilen willst, braucht es immer noch redundante Deklarationen. Die wird es in C++ immer brauchen. C++ is explizit so designed, dass es sich in einem einzelnen Pass über den Code parsen lässt. Und das geht generell eben nur, wenn der Compiler sich darauf verlassen kann, dass Dinge immer erst deklariert werden, bevor sie verwendet werden. All diese neuartigen Sprachen, die ohne vorherige Deklarationen auskommen, tun das nur, weil sie entweder mehrfach über den ganzen Code laufen oder Dinge erst zur Laufzeit auflösen…
Alexander Kornrumpf
Moderator
Beiträge: 2106
Registriert: 25.02.2009, 13:37

Re: Forward Declaration "manuell" oder #include?

Beitrag von Alexander Kornrumpf »

Also für mich ist vor allem wichtig dass der Code auf einer PDP-11 baut. Da nehme ich gerne entsprechende Design-Entscheidungen in Kauf *scnr*.
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von dot »

Alexander Kornrumpf hat geschrieben: 13.04.2023, 15:20 Also für mich ist vor allem wichtig dass der Code auf einer PDP-11 baut. Da nehme ich gerne entsprechende Design-Entscheidungen in Kauf *scnr*.
Ist nicht unbedingt nur eine Frage obs auf einer PDP-11 läuft, sondern generell eine Frage der Skalierbarkeit. Zu vermeiden, dass Teile des Code mehrfach geparsed werden müssen, ist ja der Grund wieso Modules überhaupt erst eingeführt wurden. Und die entsprechenden Benchmarks zeigen relativ klar, dass das ziemlich was ausmachen kann…
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von starcow »

Matthias Gubisch hat geschrieben: 12.04.2023, 17:35 Persönlich fahre ich mit dem Ansatz "include what you use" ganz gut. Das bedeutet dass man die Header für die benutzten Typen explizit einbindet und sich nicht auf transitive Abhängigkeiten verlässt.
Wenn man ausserdem konsequent mit forward declarations arbeitet erledigt sich das Thema an vielen Stellen von selber.
Jonathan hat geschrieben: 12.04.2023, 22:53 https://include-what-you-use.org/
Lies dir zumindest durch, was die Philosophie dahinter ist, die Regeln sind dort alle erklärt und begründet. Vermutlich findest du sie sinnvoll und willst dich an sie halten, ob du dann dieses Tool zum automatisieren benutzen willst, ist dann nochmal eine andere Frage.
Interessant! Das Konzept leuchtet ein.
Meintest du mit "Regeln" die unter "Why include what you use?" verlinkte Github-Seite?

Beim Lesen der Ausführungen habe ich realisiert, dass natürlich ein wesentlicher Punkt bei "Forward Declarations" die Tatsache ist, dass - sollte sich die Signatur einer Funktion ändern - alle Forward Declarations händisch angepasst werden müssten (im Unterschied zu includes).
Ein Lösungsansatz, um dem entgegen zuwirken wäre nun, die Forward Declarations in separate Headerfiles auszulagern und diese stattdessen zu includieren?
Krishty hat geschrieben: 13.04.2023, 00:39 Hat niemand eine Meinung dazu, dass man für diese Include-Optimierungs-Müll ständig zwischen Code-Duplikation und Performance abwägen muss?
Vielleicht eine blöde Frage, aber du meinst mit "Performance" jetzt die Compile-Zeiten - nicht die Ausführungsgeschwindigkeit des Binarys? Die dürften sich dadurch ja eigentlich nicht ändern, oder?
Krishty hat geschrieben: 13.04.2023, 00:39 Das ist für mich nämlich das eigentlich interessante Problem. Wenn C-#includes und C++-Klassen nicht by-design FUBAR wären (sie fordern grundsätzlich exponentielles Wachstum), wäre die Performance in Ordnung. Dann käme niemand auf die Idee, Deklarationen – die ja oft selber bereits das erste Duplikat einer Definition in namensähnlicher CPP sind – in leicht anderer Form nochmals zigfach über die Codebase zu verstreuen.

Nun stehen wir vor der Frage, wo wir die Grenze ziehen. Ein Include weglassen können, und dafür eine Template-Deklaration in 20 Dateien duplizieren? Schrompf zieht sie nach Performance. Welche eigentlich? Mal gemessen?
Du hast mich hier grad abgehängt. Forward Declaration ergeben schnellere Compile-Zeiten. Sehr Gut! Doch die Schattenseite? (btw: Ich habe noch kaum Erfahrungen mit Templates und verstehe vielleicht deshalb nicht, worauf du hinaus willst)

LG, starcow
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
Jonathan
Establishment
Beiträge: 2352
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von Jonathan »

starcow hat geschrieben: 15.04.2023, 18:40 Interessant! Das Konzept leuchtet ein.
Meintest du mit "Regeln" die unter "Why include what you use?" verlinkte Github-Seite?
Ehrlich gesagt hatte ich das nur schnell verlinkt. Als ich das Tool das letztes mal verwendet habe gab es meine ich so eine Kurzübersicht, aber ich find sie gerade selber nicht. Aber die Idee war im Wesentlichen:

- Nichts einbinden, was man nicht benutzt (passiert oft, wenn man Code umbaut und vergisst, Includes zu entfernen)
- Wenn eine Forward-Declaration reicht, auf das Inkludieren verzichten
- Alles was man benutzt explizit und nicht indirekt einbinden
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von Krishty »

starcow hat geschrieben: 15.04.2023, 18:40Du hast mich hier grad abgehängt. Forward Declaration ergeben schnellere Compile-Zeiten. Sehr Gut! Doch die Schattenseite? (btw: Ich habe noch kaum Erfahrungen mit Templates und verstehe vielleicht deshalb nicht, worauf du hinaus willst)
Die Schattenseite (und was ich mit Duplizierung meine) ist ganz exakt, was du hier geschrieben hast:
Beim Lesen der Ausführungen habe ich realisiert, dass natürlich ein wesentlicher Punkt bei "Forward Declarations" die Tatsache ist, dass - sollte sich die Signatur einer Funktion ändern - alle Forward Declarations händisch angepasst werden müssten (im Unterschied zu includes).
Du tauschst Compile-Zeit (das meinte ich mit meinem schwammigen Performance) gegen Wartbarkeit. Forward Declaration statt #include macht das Kompilieren schneller, aber beschert dir mehr Arbeit (mitunter schwer auffindbare Fehler), wenn du was anpassen musst.

Wenn wir über Laufzeit-Performance sprechen, betet dir jeder runter „Premature Optimization is the root of all evil“; „miss erstmal, ob der Code wirklich so langsam ist wie du glaubst“; „der Compiler kann das besser als du“. Aber bei Forward Declarations vs. #include sieht man von der Vorsicht wenig bis gar nichts. Dabei ist der Optimierungsrahmen enger (du wirst es auch mit extrem viel Aufwand nicht schaffen, das Kompilieren 100× schneller zu machen, während das bei Ausführungsgeschwindigkeit recht leicht ist) und der Gewinn geringer (ein Programm wird seltener kompiliert als es ausgeführt wird).

Ich behaupte hier mal locker, dass ich die schnellsten Kompilierzeiten von uns allen habe und höchstens drei, vier andere Leser hier mehr Zeit in das Problem investiert haben. Von daher bin ich überhaupt nicht dagegen. Ich finde die Diskussion nur extrem verzerrt, wenn man sie mit anderen Wartbarkeitsfragen vergleicht.

Und ich halte daran fest: Wenn du schnelleres Kompilieren haben möchtest (mglw. schneller, als es mit Headern jemals möglich wäre), und ohne die Nachteile von Forward Declarations, dann nutz Modules.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Mirror
Establishment
Beiträge: 248
Registriert: 25.08.2019, 05:00
Alter Benutzername: gdsWizard
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von Mirror »

Krishty hat geschrieben: 15.04.2023, 21:19 Du tauschst Compile-Zeit (das meinte ich mit meinem schwammigen Performance) gegen Wartbarkeit.
Ich haue immer Alles in die Include's. Dafür habe ich unterschiedliche Include's, je nachdem was ich machen will. Aber ich habe wahrscheinlich auch schon mehr geschrieben als Du. Ich würde Dir auf jeden Fall empfehlen auch Include's zu machen. Die Milli-Sekunden die Du beim Kompilieren sparst, sind es nicht wert. Wenn wir noch mit Disketten arbeiten würden, dann würde ich anders antworten, aber bei Festplatten, SSD's und superschnellen Speicher... Windows 11 machte haute standardmäßig, was auch schon Commodities beim Amiga gemacht haben (ich weis den Namen nicht mehr), es hält öfter benutzte Dateien im Speicher, der als frei gelistet ist, um mehr Geschwindigkeit zu erhalten.

Mal ganz abgesehen von der Wiederverwendbarkeit.
Hat den StormWizard 1.0 und 2.0 verbrochen. http://www.mirrorcad.com
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von starcow »

Gute Punkte, die ihr da aufführt.
Aber könnte man vielleicht sagen, dass "Forward Declaration" von Structs und Klassen (und NICHT von Funktionen) wann immer möglich #includes vorzuziehen sind - sofern man denn ausschliesslich mit Pointern arbeitet?
Die Signatur einer Funktion könnte sich ändern - ja. Dass das in der Konsequenz fehleranfällig und aufwändig zu warten ist, leuchtet ein. Doch bei Vorwärtsdeklarationen von Structs und Klassen gibt es ja eigentlich nichts was sich in diesem Sinne ändern könnte. Das einzige was man da deklariert ist ja den Struct oder Klassen-Namen.
Vielleicht übersehe ich aber etwas?

Gruss, starcow
Zuletzt geändert von starcow am 19.02.2024, 20:31, insgesamt 1-mal geändert.
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von Krishty »

starcow hat geschrieben: 25.04.2023, 18:42sofern man denn ausschliesslich mit Pointern arbeitet?
Hat mit Zeigern überhaupt nichts zu tun.
Die Signatur einer Funktion könnte sich ändern - ja. Dass das in der Konsequenz fehleranfällig und aufwändig zu warten ist, leuchtet ein. Doch bei Vorwärtsdeklarationen von Structs und Klassen gibt es ja eigentlich nichts was sich in diesem Sinne ändern könnte. Das einzige was man da deklariert ist ja der Struct oder Klassen-Name.
Vielleicht übersehe ich aber etwas?
Es ist doch genau so möglich, eine Klasse durch ein gleichnamiges enum class zu ersetzen (das habe ich auch in letzter Zeit oft getan). Du hast dann eine Verletzung der One Definition Rule. Wie schwer die mit aktuellen Tools zu finden ist, kann ich nicht sagen.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von starcow »

Krishty hat geschrieben: 25.04.2023, 20:10
starcow hat geschrieben: 25.04.2023, 18:42sofern man denn ausschliesslich mit Pointern arbeitet?
Hat mit Zeigern überhaupt nichts zu tun.
Ich meinte das so:
Forward Declaration dann, wenn man den konkreten Aufbau der Klasse/Struct nicht zu kennen braucht, da man nur einen Zeiger auf die Klasse/Struct hat (welchen man z. B. einfach weiterreichen will).
Wenn ich ja eine Situation habe, in der ich die "Interna" der Klasse/Struct kennen muss, weil ich z.B auf eine Komponente zugreife, komme ich ja mit einer Forward Declaration ohnehin nicht aus.
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Mirror
Establishment
Beiträge: 248
Registriert: 25.08.2019, 05:00
Alter Benutzername: gdsWizard
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von Mirror »

Auch das sollte man in die includes machen, da du das ja in mehreren cpp Dateien brauchen kannst. Außerdem kann es ja sein, das man die Vorwärtsdeklaration schon innerhalb der include benötigt, was ja öfter vorkommt.
Hat den StormWizard 1.0 und 2.0 verbrochen. http://www.mirrorcad.com
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von Krishty »

starcow hat geschrieben: 25.04.2023, 23:43Forward Declaration dann, wenn man den konkreten Aufbau der Klasse/Struct nicht zu kennen braucht, da man nur einen Zeiger auf die Klasse/Struct hat (welchen man z. B. einfach weiterreichen will).
Die erste Hälfte ist richtig; die zweite nicht. Sagen wir ganz plump, du hast eine Klasse String. Mit dem tollen Spezial-Feature, dass man einen MD5-Hash aus dem String erzeugen kann:

  class String {
  public:

    String();
    // … viel Blabla bzgl. Konstruktion, Zuweisung, usw.

    MD5 getHash() const;

  private: …
  };


Muss jetzt jeder, der deinen String #includet, auch MD5.h einbinden, obwohl er die Hash-Funktion niemals benutzt?

Nein. Forward Declaration reicht: class MD5;
Erst, wenn jemand tatsächlich getHash() aufruft, muss MD5 vollständig definiert sein (also der Header eingebunden). Vorher nicht.

… und das hat nichts mit Zeigern zu tun. Im ganzen Beispiel kommt kein Zeiger vor.

Ist kein Beispiel für gute Software-Architektur sondern für Forward Declaration ohne Zeiger; und Mirrors Einwand gilt ebenso. Einfach um zu betonen, dass Forward Declarations nichts mit Zeigern zu tun haben.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von starcow »

Gutes Beispiel, jetzt sehe ich, was du meinst. Danke!
Dann würde man hier also - wie du sagst - in der Datei "string.h" die Klasse MD5 lediglich vorwärts deklarieren. Wenn dann jemand die Funktion "getHash()" nutzen will, muss derjenige "md5.h" in seinem Quelltext "selbstständig" includieren.

Der Vollständigkeit halber:
Müsste es eigentlich nicht sogar möglich sein, die Funktion getHash() auch ohne #inlcude "md5.h" aufrufen könnte, solange man den Rückgabewert nicht "auffängt" oder ihn aktiv verwirft (was in diesem Beispiel natürlich keinen Sinn ergibt)?

Code: Alles auswählen

getHash();              // Ok. Kein include von "md5.h" nötig
(void)getHash();        // Ok. Kein include von "md5.h" nötig
md5Value = getHash();   // Compile error. Include von "md5.h" nötig
Gruss, starcow
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von Krishty »

starcow hat geschrieben: 28.04.2023, 15:05Müsste es eigentlich nicht sogar möglich sein, die Funktion getHash() auch ohne #inlcude "md5.h" aufrufen könnte, solange man den Rückgabewert nicht "auffängt" oder ihn aktiv verwirft (was in diesem Beispiel natürlich keinen Sinn ergibt)?

Code: Alles auswählen

getHash();              // Ok. Kein include von "md5.h" nötig
(void)getHash();        // Ok. Kein include von "md5.h" nötig
md5Value = getHash();   // Compile error. Include von "md5.h" nötig
Ist es (leider) nicht. Der Aufrufer muss dem Aufruf genug Platz zur Verfügung stellen, um den Rückgabewert zu speichern. Ob man ihn verwendet, oder nicht.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Jonathan
Establishment
Beiträge: 2352
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von Jonathan »

(kann man irgendwie doppelte Posts selber löschen? :D)
Zuletzt geändert von Jonathan am 28.04.2023, 23:23, insgesamt 1-mal geändert.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Jonathan
Establishment
Beiträge: 2352
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von Jonathan »

Ist es (leider) nicht. Der Aufrufer muss dem Aufruf genug Platz zur Verfügung stellen, um den Rückgabewert zu speichern. Ob man ihn verwendet, oder nicht.
Ein paar mehr Details: Die Funktion ist immer die selbe, egal von wo sie aufgerufen wird (vergessen wir kurz mal templates und inlining). Es gibt sogenannte Aufrufkonventionen (https://en.wikipedia.org/wiki/Calling_convention) die Regeln wie Parameter und Rückgabewerte übergeben werden, ein Teil davon macht die Funktion selber, der andere Teil wird vom Aufrufer übernommen. Wenn die Funktion also z.B. immer ihren Rückgabewert auf den Stack schreibt, muss dieser immer vom Aufrufer wieder aufgeräumt werden, und dafür muss er z.B. mindestens wissen wie groß der Wert war. Vermutlich hat man in C++ auch irgendwo mal den Fall, das der Destruktor eines zurückgegebenen Objektes vom Aufrufer der Funktion aufgerufen werden muss, und dafür muss der ja auch bekannt sein.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von Krishty »

Wir haben seit ein paar Jahren auch Guaranteed Copy Elision. AFAIK wird Object foo() dabei automatisch zu void foo(Object & result) umgewandelt.

Auch wenn Guaranteed Copy Elision wahrscheinlich nicht greift, wenn das Ergebnis nicht irgendwo gefangen wird, wird der Compiler keine zwei Versionen einer Funktion generieren wollen/dürfen. Es dürfte klar sein, was kaputtgeht, wenn der Aufrufer nicht die Größe des Ergebnisses kennt.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von starcow »

Krishty hat geschrieben: 28.04.2023, 20:47 Der Aufrufer muss dem Aufruf genug Platz zur Verfügung stellen, um den Rückgabewert zu speichern. Ob man ihn verwendet, oder nicht.
Macht Sinn und klingt vernünftig.
Jonathan hat geschrieben: 28.04.2023, 23:22 Ein paar mehr Details: Die Funktion ist immer die selbe, egal von wo sie aufgerufen wird (vergessen wir kurz mal templates und inlining). Es gibt sogenannte Aufrufkonventionen (https://en.wikipedia.org/wiki/Calling_convention) ...
Danke fürs Stichwort! Ich war mit dem Konzept noch definitiv zu wenig vertraut.
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von Krishty »

Neben die Tüte: Ich setze nun testweise Kapselung durch zwei Header durch. Das ist mein nächster Versuch, so etwas wie Pimpl ohne Overhead zu erreichen.

Der Master-Header enthält ausschließlich die öffentliche API der Klasse minus Instanzierung/Freigabe:

Code: Alles auswählen

// Enemy.hpp

struct Enemy;
float healthOf(Enemy const &);
void setHealth(Enemy &, float);
Für 99 % der Aufrufe reicht so ein Header aus. Er enthält keine Abhängigkeiten; das wird komplett über Forward Declarations gelöst.

Dann gibt es noch einen halb-privaten Header. Den braucht man, wenn man die Klasse tatsächlich instanzieren/kopieren/in Container ablegen möchte, oder Zugriff auf private Attribute braucht.

Code: Alles auswählen

// Enemy.layout.hpp

struct Enemy {
  float health;
};
Die Implementierung ist dann wie gewohnt, nur dass sie beide Header #includet, während die meisten Aufrufer mit dem öffentlichen Header auskommen:

Code: Alles auswählen

// Enemy.cpp

#include "Enemy.hpp"
#include "Enemy.layout.hpp"

float healthOf(Enemy const & e) {
  return e.health;
}
void setHealth(Enemy & e, float h) {
  e.health = h;
}
Bisher habe ich nochmal eine drastische Vereinfachung der #include-Abhängigkeiten, halt ähnlich Pimpl. Das funktioniert aber nur für große Klassen, die man oft benutzt und selten instanziert – für Klassen, die überall by-value herumgereicht werden, wäre das Overkill (weil man dann eh immer beide Header einbinden müsste).

Wahrscheinlich schmeiße ich das wieder weg, wenn ich auf Modules umsteige, aber vorher wollte ich es einmal ausprobiert haben.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von Schrompf »

Ich finde die Idee schön, aber ich wundere mich, ob man jemals das Layout ohne die Methoden bräuchte? Ich würde also Enemy.h in Enemy.layout.h inkludieren. So hätte man je nach Verwendungszweck immer nur ein #include
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von Krishty »

Stimmt natürlich 👍 Ich fahre hier eine keine-Includes-in-Header-Policy und hatte vergessen, das für den Post umzustellen.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
starcow
Establishment
Beiträge: 523
Registriert: 23.04.2003, 17:42
Echter Name: Mischa Schaub
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von starcow »

Finde die Idee sehr gut!
Könnte man nicht auch einfach weiterhin eine einzige Header-Datei verwenden und in dieser mittels Präprozessordirektive das Layout nur bei explizitem Wunsch zugänglich machen?
So könnte man auf zusätzliche Dateien verzichten?

Code: Alles auswählen

// Forward declaration only
struct foo;

// If the full layout is needed define FOO_LAYOUT before include "foo.h"
#ifdef FOO_LAYOUT
struct foo
{
	int miau;
};
#endif

Code: Alles auswählen

// main.c

#define FOO_LAYOUT
#include "foo.h"
...
Freelancer 3D- und 2D-Grafik
mischaschaub.com
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von Krishty »

Könnte man sicher – aber Header mit Zustand (die sich auf underschiedliche Art parsen lassen) find ich schwer zu verstehen und sie sind z. B. nicht mit #pragma once kompatibel.

(Inwiefern meine Lösung da „einfacher“ zu verstehen wäre, sei mal dahingestellt.)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Thoran
Establishment
Beiträge: 224
Registriert: 15.05.2009, 12:51
Wohnort: Stuttgart
Kontaktdaten:

Re: Forward Declaration "manuell" oder #include?

Beitrag von Thoran »

Ich mach das normaler Weise so:
  • Code Bereich (Modul, Paket) mit Interface-Header im öffentlichen Teil versehen (das sorgt für die Trennung von Code-Konsument und interner Implementierung). Dieser inkludiert in der Regel nur Prototyp-Header und was zusätzlich absolut notwendig ist für die Signatur des Interfaces
  • Prototyp-Header im öffentlichen Teil des Codebereichs (e.g. Modul, Paket). Dieser enthält ausschließlich Foreward-Decl.
  • In der Implementierung (was ja theoretisch auch mehrere sein können) der Interface-Header wird dann das konkrete Headerfile des verwendeten Objekts inkludiert. Dabei folge ich dem "include-what-you-use" Ansatz. Die Implementierung liegt dann im Modul/Paket-privaten Bereich.
Das sorgt dafür das ein Konsument nur die Interface- und Prototyp-Header benötigt und auch nur diese im Code verteilt genutzt werden. Allerdings sind diese im vergleich zu einem Full-Class-Header meist kleiner.
Datenstrukturen (structs, enums, callbacks) werden dort deklariert wo sie per Definition notwendig sind, in der Regel ist dass dann das Interface-Headerfile.
Wer Rechtschreibfehler findet, darf diese gerne behalten.
Mein Entwicklertagebuch
Aktuelle Projekte: Universum: Domination (ehemalig AlphaOmega),Universum: Sternenjäger, PixelWars: Highscore-based Top-Down-Spaceshooter
Spieleengine Unreal 5
Antworten