#ifdef -> einrücken oder nicht?



  • ah ok- danke für die Antwort.

    jetzt mal wieder ne blöde frage: eine funktion im header - damit ist gemeint sie soll einfach roh in einem header-file (z.B. variante1.hpp) liegen?

    Wo kann eine Funktion noch liegen wenn nicht im header? Als methode einer Klasse?



  • gruppa schrieb:

    Wo kann eine Funktion noch liegen wenn nicht im header? Als methode einer Klasse?

    Die funktions definition muss im header liegen damit die compiler problemlos geinlined werden können. die deklaration muss sowieso im header stehen.

    definition:
    void foo() {}

    deklaration:
    void foo();



  • Shade Of Mine schrieb:

    Dafür hast du ja die proxy dateien.
    foo.cpp sieht zB so aus:

    #ifdef X
    #  include "X/foo.cpp"
    #elif Y
    #  include "Y/foo.cpp"
    ...
    

    Sowas wuerde ich nie machen, da ich es als viel sauberer empfinde, es ueber entsprechende Build-/Linkeinstellungen (Skripte, what ever) zu steuern. Ich finde es einfach haesslich.



  • knivil schrieb:

    Sowas wuerde ich nie machen, da ich es als viel sauberer empfinde, es ueber entsprechende Build-/Linkeinstellungen (Skripte, what ever) zu steuern. Ich finde es einfach haesslich.

    dh du musst dauernd die build scripte anpassen...?

    das wäre zB für mich ein no-go.
    der vorteil von meiner #include variante ist, das alles transparent abläuft. du kompilierst die foo.cpp und die flags definieren was genau passiert.

    denn wozu muss man wissen dass foo.cpp in wirklichkeit 3 unterschiedliche implementierungen hat? und vorallem wann diese verwendet werden. diese logik würde ich zB nie in ein buildscript packen. da sie sich nämlich konstant ändern kann. und build scripte sind in meinen augen etwas konstantes woran man nicht unnötig viel herum manipulieren sollte...



  • dh du musst dauernd die build scripte anpassen...?

    Sagen wir mal "erweitern" und sollte nicht schwerer sein als ein weiteres #ifdef in deiner cpp-Datei hinzuzufuegen (kommt auf die Flexibilitaet deines Buildsystems an, k.A. was Visual Studio da so anbietet). Die Buildskripte musst du bei deiner Variante auch anfassen, denn wo soll X, Y etc definieren werden, wenn nicht als Compileroption.

    wissen dass foo.cpp in wirklichkeit 3 unterschiedliche implementierungen hat?

    Ja, musst du auch sonst, steht ja in deiner cpp-Datei. Musst du aber auch wieder nicht, da ja das Buildskript alles macht. Wenn foo 3 unterschiedliche Implementierungen fuer 3 unterschiedliche Plattformen (oder Features) hat, dann steht in den Buildskripten das drin, welche uebersetzt und gelinkt werden soll. Und ob es nun in einer cpp-Datei drin steht oder im Buildskript ist erstmal vom Aufwand egal. Ist es aber ausserhalb definiert, besteht eine klare Trennung zwischen Code und Build-/Linkoptionen. Bestehenden Code moechte ich nicht anfassen, wenn ich z.B. eine Plattform unterstuetzen moechte (zumindestens in einer idealen Welt).



  • @knivil: kannst du ein minimales beispiel bringen wie du das machen würdest - interessiert mich... wäre nett danke



  • Dieses minimale Beispiel zeigt ein makefile, wie es aussehen koennte (prinzipiell). Mittels "make ogl" wuerde man die Version bauen, deren Rendererimplementation OpenGl benutzt, mittels "make directx" halt fuer DirectX. Das dargestellte ist sehr vereinfacht. Auch sieht man hier 2 Zeilen, die fast gleich aussehen -> "Codeduplikate" -> eher schlecht. Das kann alles umgangen werden, mit zusaetzlichen Variablen etc.

    Auch ist make ein eher krudes Tool, so dass damit Arbeiten nicht wirklich Spass macht. Andere Alternativen sind autoconf (das makefiles fuer die ensprechende Plattform/Features erzeugt) oder Apache Ant. Es gibt viele verschiedene Tools. Ein Vorteil ist z.B. dass man mit einem Aufruf auch Dokumentation, Bilddaten ... erzeugen lassen, automatische Tests starten kann etc. Fuer den Heimeinsatz ist das aber ueberdimensioniert, auch sollte man sich ueberlegen, ob es einem Arbeit abnimmt. Deswegen bleibe ich bei make, obwohl es eher hoelzern und kryptisch ist. Z.B. nutze ich make insbesondere beim Schreiben von Texten mit Latex.

    OBJECTS = ... many .o files
    
    ogl: $(OBJECTS) RendererOpenGl.o
      g++ $(OBJECTS) RendererOpenGl.o -o app_ogl
    
    directx: $(OBJECTS) RendererDirectX.o
      g++ $(OBJECTS) RendererDirectX.o -o app_direct
    


  • danke kniviel aber leider versteh ichs noch nicht.

    So muss man doch komplett doppelten code schreiben oder nicht? Ich muss doch sowohl für opengl also auch directx dann eine funktionsfähige codebasis haben?

    kannst du das auf unser minimalbeispiel übertragen?



  • da ich meine makefiles nicht dauernd pflegen will, werde ich Shades vorschläge übernehmen.



  • knivil schrieb:

    dh du musst dauernd die build scripte anpassen...?

    Sagen wir mal "erweitern" und sollte nicht schwerer sein als ein weiteres #ifdef in deiner cpp-Datei hinzuzufuegen (kommt auf die Flexibilitaet deines Buildsystems an, k.A. was Visual Studio da so anbietet). Die Buildskripte musst du bei deiner Variante auch anfassen, denn wo soll X, Y etc definieren werden, wenn nicht als Compileroption.

    Ich definiere einfach einen ordner in dem die .cpp Dateien liegen. Wenn neue hinzukommen, kommen eben neue hinzu, dafür muss ich kein build script anpassen.

    vorallem wenn ich mit VStudio arbeite, einfach nur die .cpp Datei zum Projekt hinzufügen und fertig. Das ist das tolle an proxy dateien: 0 aufwand.

    wissen dass foo.cpp in wirklichkeit 3 unterschiedliche implementierungen hat?

    Ja, musst du auch sonst, steht ja in deiner cpp-Datei. Musst du aber auch wieder nicht, da ja das Buildskript alles macht. Wenn foo 3 unterschiedliche Implementierungen fuer 3 unterschiedliche Plattformen (oder Features) hat, dann steht in den Buildskripten das drin, welche uebersetzt und gelinkt werden soll.

    Bei mir eben nicht da ich mich nicht auf 2-3 targets limitiere. ich könnte zB 5 sets haben (openMP ja/nein, directX/openGL/GDI, zielplattform OS X/Windows/Linux,..) und mit meiner variante kann ich einfach sagen:
    OpenMP + Linux + OpenGL und das wars.
    oder eben
    Windows + DirectX und das wars.

    kann das deine variante auch? 😉

    Bestehenden Code moechte ich nicht anfassen, wenn ich z.B. eine Plattform unterstuetzen moechte (zumindestens in einer idealen Welt).

    du fasst nur die proxy datei an.
    aber das ist natürlich witzlos, weil für eine neue plattform eine riesen menge an änderungen in der praxis nötig sind.



  • Shade Of Mine schrieb:

    vorallem wenn ich mit VStudio arbeite, einfach nur die .cpp Datei zum Projekt hinzufügen und fertig. Das ist das tolle an proxy dateien: 0 aufwand.

    Ja, das geht auch, diese Zeile nimmt mir viel Arbeit ab:

    .SUFFIXES: .cpp .o
    
    .cpp.o:
    	$(CC) -c $(CFLAGS) $(INCLUDE) -o $@ $<
    

    Wie gesagt, kryptisch.

    (openMP ja/nein, directX/openGL/GDI, zielplattform OS X/Windows/Linux,..) [...]
    kann das deine variante auch? 😉

    Ja.

    Wie gesagt, im Hobbybereich oder bei kleinen Projekten macht es vielleicht kaum Sinn. Genauso wenig schreibt jemand seine Unittests vorher, etc ... .

    da ich meine makefiles nicht dauernd pflegen will

    Es ist wie alles, vorher gut geplant und staendige Aenderungen sind nicht noetig.

    Ich habe nicht umsonst gesagt, dass es nur ein Minimalbeispiel ist, es ist nichts woran man make messen sollte. Ausserdem gibt es viele Alternativen.



  • knivil schrieb:

    da ich meine makefiles nicht dauernd pflegen will

    Es ist wie alles, vorher gut geplant und staendige Aenderungen sind nicht noetig.

    Ich habe nicht umsonst gesagt, dass es nur ein Minimalbeispiel ist, es ist nichts woran man make messen sollte. Ausserdem gibt es viele Alternativen.

    oder ich schreib #ifdef in die c++-headerdatei und lass das makefile einfach wie es ist. 💡 👍
    es compiliert einfach alle *.cpp-dateien durch. ich wills nähmlich auch nicht dauernd pflegen, wenn ich ne klasse hinzufüge oder lösche.



  • knivil schrieb:

    (openMP ja/nein, directX/openGL/GDI, zielplattform OS X/Windows/Linux,..) [...]
    kann das deine variante auch? 😉

    Ja.

    Und wie?

    Bedenke, du kannst jede beliebige kombination an sets verwenden. und die sets sollen beliebig erweiterbar bleiben.

    ich muss keinen gedanken an sowas verschwenden, es funktioniert automatisch. eine beschreibung wie du das bei dir implementieren würdest würde mich da sehr interessieren.

    Es ist wie alles, vorher gut geplant und staendige Aenderungen sind nicht noetig.

    neues set - neue erweiterung.
    oder wie soll das funktionieren?



  • Ich will euch nicht ueberzeugen. Auch koennt ihr euch ruhig lustig machen. Es sind Tools, die einem Arbeit abnehmen. Wenn keine verschiedenen Implementationen, keine Prebuild-/Postbuildsteps noetig sind oder z.B. kein Content aus Daten generiert wird ... dann braucht man ja auch kein Tool, das das automatisiert. Wenn ihr wirklich wissen wollt, wie es funktioniert, dann sind die Manuals besser als ein Forenpost.



  • mich würde es einfach interessieren ob es eine aktzeptable alternative zu den #includes ist.

    mir fällt keine möglichkeit ein es dynamisch genug zu bauen ohne komplexe buildscripte zu verwenden. während die #include variante halt 'deppensicher' ist.

    und vorallem ist sie konsistent mit der variante bei header files - da man es dort ja auch so macht.

    aber uU bieten buildscripte hier einen gewissen vorteil - nur dazu musst du etwas deutlicher werden. denn ich sehe beim besten willen nicht wie ich das ohne komplexe scripte implementieren soll.

    wo die inklude variante zB probleme hat ist wenn du mehrere .cpp dateien für die implementierung einer proxy .cpp datei brauchst. kommt selten vor und ist eher ein design fehler dann, aber es kommt vor und da muss man eben per #include die notwendigen .cpp dateien wieder inkludieren und das ist einfach doof.

    deshalb bin ich immer auf der suche nach besseren möglichkeiten...

    PS:
    die #include variante nimmt einem nicht das schreiben von build scripten ab. die braucht man sowieso. es geht also nicht pro/contra build script, sondern welche lösungen für das problem (mit den mehreren sets an flags) gibt es.



  • Ok...@Shade - 3 kurze weitere fragen - langsam überzeugt mich dein Vorgehen 🙂

    1. Wenn im einen Zweig inline eine funktion ist, muss sie es doch im anderen zweig nicht sein oder?

    2. wie machst du es wenn eine funktion z.B. foo() in einem zweig ist und im anderen foo(int a) ? beim aufruf im hauptcode muss dann doch ein ifdef stehen oder nicht? ich versteh deinen ansatz mit den proxy-files nur wenn beide funktionen die gleiche signatur haben.

    3. für funktionen die etwas rechnen aber in z.B. X/xyz.hpp deklariert sind. Wie kann ich diese in ein X/xyz.cpp file schreiben? ALso wie gehst du mit definitionen um die im X/ Ordern liegen sollen?

    Danke



  • mich würde es einfach interessieren ob es eine aktzeptable alternative zu den #includes ist.

    Wenn 3 Zeilen einfacher sind als 10, dann nicht. Gegenfrage: Wann werden #ifdefs kompliziert?

    nur dazu musst du etwas deutlicher werden.

    Das wuerde leider zu umfangreich werden. Noetig wird es erst, wenn du z.B. Software fuer die Allgemeinheit schreibst. Unter Linux ./configure, make, make install. Unter Windows hat man ein Setupprogramm. Dort kompiliert man aber auch selten was als Benutzer. configure testet auf Header und benoetigte Bibliotheken und erzeugt dir dann dein makefile gemaess den angegebenen Optionen (z.B. mit Threads, ob pthreads oder was auch immer, mit gnome oder ohne ... aha SSE2 also diese Datei uebersetzen und nicht die fuer Cell optimiert und auch sonst die entsprechenden Compileroptionen setzen ...). Um dieses Skript zu erzeugen, gibt es wiederum (auch graphische) Tools.

    enn ich sehe beim besten willen nicht wie ich das ohne komplexe scripte implementieren soll.

    Einfache Probleme sollten einfache Loesungen haben. Meine makefile-Variante ist aber auch nicht komplizierter oder gar komplex. Mein Standard-Makefile hat ganze 20 Zeilen (ohne Leerzeilen). Ich loese das Problem halt ueber den Linker, du ueber den Kompiler. Und fuer komplexe Buildskripte gibt es auch Tools, die einem viel Arbeit abnehmen.



  • gruppa schrieb:

    1. Wenn im einen Zweig inline eine funktion ist, muss sie es doch im anderen zweig nicht sein oder?

    Exakt. Wenn du zB eine Funktion lock() hast, die in einem nicht multi-threading programm nichts tut, dann packst du sie einfach in den header

    void lock(Foo whatever) {
    }
    

    und der compiler optimiert sie komplett raus.
    Wenn sie dann aber etwas in einem multi-threaded programm tut, dann packst du nur noch die deklaration in die hpp Datei und die implementierung in die passende cpp Datei.

    1. wie machst du es wenn eine funktion z.B. foo() in einem zweig ist und im anderen foo(int a) ? beim aufruf im hauptcode muss dann doch ein ifdef stehen oder nicht? ich versteh deinen ansatz mit den proxy-files nur wenn beide funktionen die gleiche signatur haben.

    Die funktion muss die gleiche signatur haben.
    Wenn wir zB von locks ausgehen, wo du in einem parallelen programm eben locks halten musst die du in einem seriellen logischerweise nicht brauchst, wuerde mein code dennoch so aussehen:

    void foo() {
      lock l(bar);
    
      do_something(l);
    }
    

    Nur dass lock in einem seriellen programm halt nichts tut, also alle funktionen haben einen leeren koerper. oft ist es dann auch sinnvoll statt
    lock.unlock() ein "unlock(lock)" zu schreiben, und unlock ruft dann entweder lock.unlock auf oder tut garnichts.

    1. für funktionen die etwas rechnen aber in z.B. X/xyz.hpp deklariert sind. Wie kann ich diese in ein X/xyz.cpp file schreiben? ALso wie gehst du mit definitionen um die im X/ Ordern liegen sollen?

    Diese Frage verstehe ich nicht. Erklaer bitte genauer was du meinst.

    Danke[/quote]

    knivil schrieb:

    Wenn 3 Zeilen einfacher sind als 10, dann nicht. Gegenfrage: Wann werden #ifdefs kompliziert?

    Eigentlich zu keinem Zeitpunkt. Sie sind nur dann unpraktisch, wenn man mehrere helper klassen braucht und diese dann in das .cpp File includen muss anstatt sie als eigene uebersetzungseinheit zu verwenden.

    Das wuerde leider zu umfangreich werden. Noetig wird es erst, wenn du z.B. Software fuer die Allgemeinheit schreibst. Unter Linux ./configure, make, make install. Unter Windows hat man ein Setupprogramm. Dort kompiliert man aber auch selten was als Benutzer. configure testet auf Header und benoetigte Bibliotheken und erzeugt dir dann dein makefile gemaess den angegebenen Optionen (z.B. mit Threads, ob pthreads oder was auch immer, mit gnome oder ohne ... aha SSE2 also diese Datei uebersetzen und nicht die fuer Cell optimiert und auch sonst die entsprechenden Compileroptionen setzen ...). Um dieses Skript zu erzeugen, gibt es wiederum (auch graphische) Tools.

    danke, ich kenne die gaengigen tools. wie gesagt: build scripte braucht man so oder so. da fuehrt kein weg vorbei.

    nur wie implementierst du eine loesung fuer das hier vorliegende problem ohne zu komplex zu werden. das ist die frage.

    enn ich sehe beim besten willen nicht wie ich das ohne komplexe scripte implementieren soll.

    Einfache Probleme sollten einfache Loesungen haben. Meine makefile-Variante ist aber auch nicht komplizierter oder gar komplex. Mein Standard-Makefile hat ganze 20 Zeilen (ohne Leerzeilen). Ich loese das Problem halt ueber den Linker, du ueber den Kompiler. Und fuer komplexe Buildskripte gibt es auch Tools, die einem viel Arbeit abnehmen.

    die frage ist aber: wie loest du es.
    und was der linker damit zu tun hat verstehe ich auch nicht.
    oder kompilierst du immer alles und linkst dann nur die relevanten sachen? das kanns ja nicht sein, oder?



  • Ich hatte mal ne Crossplattform-Bibliothek geschrieben, da fande ich die Makefile-Lösung wesentlich einfacher. Es waren nur Implementierungen, also die .cpp-Dateien, welche konfigurationsabhängig waren, damit sah mein Verzeichnisbaum etwa so aus:

    include
    source
    source/win
    source/linux
    

    Unter Windows im VS hatte ich einfach die nur die normalen- und Windows-Sourcen im Arbeitsbereich, bei Linux sah das Makefile etwa so aus:

    g++ source/* source/linux/*
    

    Das zu Warten war wirklich einfach: Wenn 'ne neue Source-Datei dazukommt, einfach die entsprechende im Windows- und Linux-Verzeichnis ablegen und im VS die Win-Version in den Arbeitsbereich aufnehmen. Das wars.
    Bei der #ifdef-Variante bräuchtest du für jede Source-Datei eine zusätzliche Proxy-Datei. Allerdings war mein Fall auch ein Spezialfall, da es nur auf einen einzigen Compilerswitch ankam und nur Implementationen variabel waren.



  • @Badestrand:
    jetzt bau in dein system mal bitte eine variante mit Qt und eine mit gtk ein.

    und wenn wir lustig sind noch jeweils einmal mit boost::thread und einmal mit nativen threads.

    Du kannst natuerlich jetzt
    src/win
    src/qt
    src/gtk
    src/lin
    src/lin_threads
    src/win_threads
    src/boost_threads

    machen. nur wie kompilierst du dann? klar, kann man ueber das build script sagen was man will, aber wie komplex wird das abfragen und das setzen der jeweiligen defines? das ist ja das interessante.
    ein
    build USE_NATIVE_THREADS USE_LINUX USE_GTK
    waere echt super zum builden. geht das mit vernuenftigen aufwand?

    das problem ist naemlich ploetzlich: was sind native threads? je nach plattform wieder etwas anderes...

    wuerde mich echt interessieren wie das vernuenftig geht


Anmelden zum Antworten