Fehlermeldung bei globalem vector-Objekt vom Typ string



  • @Yadgar sagte in Fehlermeldung bei globalem vector-Objekt vom Typ string:

    #define STBI_NO_BMP
    #define STBI_NO_PSD
    #define STBI_NO_HDR
    #define STBI_NO_PIC
    #define STBI_NO_PNM

    //#include <stdlib.h>
    #include <iostream>
    //#include <string.h>
    #include <vector>
    #define STB_IMAGE_IMPLEMENTATION
    //#include <stb/stb_image.h>
    #define STB_IMAGE_WRITE_IMPLEMENTATION
    //#include <stb/stb_image_write.h>

    Die nicht auskommentierten Präprozessordirektiven verstehe ich (alle) nicht und ich bin mir fast sicher, dass daran etwas falsch sein muss...

    Die STBI_NO_...-Direktiven sind offizielle Flags von stb_image.h. Da diese vor der Implementierung definiert werden, werden die entsprechenden Decoder (BMP, PSD, HDR, PIC und PNM) gar nicht erst kompiliert. Das spart Platz in der finalen ausführbaren Datei. Aktiv bleiben damit standardmäßig noch beliebte Formate wie PNG, JPEG, GIF und TGA.

    Empfehlung:

    // 1. Standard-Header
    #include <iostream>
    #include <vector>
    
    // 2. Feature-Flags für stb_image
    #define STBI_NO_BMP
    #define STBI_NO_PSD
    #define STBI_NO_HDR
    #define STBI_NO_PIC
    #define STBI_NO_PNM
    
    // 3. Implementierung & Header laden (Pfade ggf. anpassen!)
    #define STB_IMAGE_IMPLEMENTATION
    #include "stb/stb_image.h" 
    
    #define STB_IMAGE_WRITE_IMPLEMENTATION
    #include "stb/stb_image_write.h"
    

    Und beachte: Die Definitionen von STB_IMAGE_IMPLEMENTATION und STB_IMAGE_WRITE_IMPLEMENTATION dürfen insgesamt nur in genau einer einzigen .cpp-Datei des Projekts stehen, da es sonst zu Dublicate Symbol-Fehlern beim Linken kommt.



  • @SeppJ sagte in Fehlermeldung bei globalem vector-Objekt vom Typ string:

    errormsgs.push_back("Befehl unbekannt!");

    Das steht im Nirgendwo. Lektion 0 in C++: Code muss in Codeblöcken stehen. Vielleicht möchtest du dir mal angucken, wie man die Werte von Vectoren bei deren Definition setzt? Oder noch viel besser: Vermeide Anti-Patterns wie veränderliche globale Objekte gleich ganz. Da wirst du nur unglücklich mit.

    O.k., dann werde ich das Fehlermeldungs-Vector-Array jedesmal genauso wie die Befehlsliste per Referenz übergeben (müssen) - sieht zwar plump aus, aber dürfte zumindest funktionieren! Und was die weiteren Postings angeht (Lambda-Funktion)... ich glaube, ich will mein C++ von 1998 wiederhaben! Breymann, oder allenfalls noch Aupperle...



  • Was ist mit structs? Ich sage schon mal gleich, dass mehr als drei Parameter ein Anti-Pattern, also falsch ist.


  • Mod

    @Yadgar sagte in Fehlermeldung bei globalem vector-Objekt vom Typ string:

    O.k., dann werde ich das Fehlermeldungs-Vector-Array jedesmal genauso wie die Befehlsliste per Referenz übergeben (müssen)

    Muss das denn überhaupt eine dynamische Struktur sein? Schau was john-0 vorgeschlagen hat. Das wäre absolut in Ordnung.

    Ich hatte zunächst angenommen, dass du da irgendwie eine Art mehrelementige globale Fehlervariable bauen wolltest (Tu das nicht!), weil mir sonst nicht klar war, wieso man sonst so etwas dynamisch bauen sollte. Aber anscheinend geht es ja um eine feste Liste von statischen Strings, und du bist bloß mit dem komplett falschen Datentyp unterwegs.

    Wenn dir das von john-0 zu kompliziert ist, ginge auch ein Array von const char Zeigern auf die Fehlermeldungen. Aber du solltest dann dringend deine Einstellung zu modernem C++ überdenken:

    Und was die weiteren Postings angeht (Lambda-Funktion)... ich glaube, ich will mein C++ von 1998 wiederhaben! Breymann, oder allenfalls noch Aupperle...

    Klingt leider nach Fortschrittsverweigerung. Das neue Zeug ist ja nicht schwieriger oder unverständlicher als früher, du kennst es bloß nicht. Beziehungsweise, das neue Zeug macht klarer, was da wirklich passiert, wohingegen ein const-char-Array viele versteckte Fallstricke hätte, die dir wahrscheinlich gar nicht bewusst sind.



  • Sorry, ich habe meinen Beitrag editiert, das sollte nicht so unfreundlich klingen.

    Vordefinierte Fehlermeldungsstrings könnte man vielleicht auch in einem Header definieren.

    Jedenfalls wurde mir erst jetzt verständlich(er), wofür du das brauchst.



  • @Yadgar sagte in Fehlermeldung bei globalem vector-Objekt vom Typ string:

    O.k., dann werde ich das Fehlermeldungs-Vector-Array jedesmal genauso wie die Befehlsliste per Referenz übergeben (müssen) - sieht zwar plump aus, aber dürfte zumindest funktionieren! Und was die weiteren Postings angeht (Lambda-Funktion)... ich glaube, ich will mein C++ von 1998 wiederhaben! Breymann, oder allenfalls noch Aupperle...

    Es gibt im Software Design der Grundsatz, dass man keine impliziten Abhängigkeiten von veränderlichen Objekten will. Daher arbeitet man lieber mit Dependency Injection, was nichts anderes als globale Variablen mit fancy Namen sind. Der Unterschied ist aber, dass die Abhängigkeit über das Interface der Funktion ersichtlich ist, während er bei bei globalen Variablen implizit erfolgt.

    Im HPC gibt es einen sehr triftigen Grund für diese Vorgehen, globale Variablen versauen die Möglichkeit der Optimierung und kosten nicht unerheblich Performance. In Fortran gibt es daher das Kozept von „pure“ Funktions und Subroutines, die keinerlei globalen Kontext haben dürfen, d.h. der Compiler überwacht das strikt.



  • @Finnegan sagte in Fehlermeldung bei globalem vector-Objekt vom Typ string:

    @john-0 sagte in Fehlermeldung bei globalem vector-Objekt vom Typ string:

    static constexpr std::array<std::string_view, N> commands

    Ja. Das ist noch besser. std::vector echt nur wenn die Einräge dynamisch sein müssen (zur Laufzeit hinzufügen oder entfernen). Auch für errormsgs.

    Das geht noch schicker:

    static constexpr std::array commands {
    	"-help",
    	"-e",
    	"-engine"
    };
    


  • @Yadgar @john-0 Ich würde das mit den "globale Variablen vermeiden" auf solche einschränken, die zu einem veränderlichen globalen Zustand eines Moduls führen. Die haben in der Tat mehrere Nachteile:

    • versteckte Abhängigkeiten, die man nur im Code selbst und nicht im Interface erkennt
    • das macht den Code schwerer zu verstehen
    • und schwerer zu debuggen, da auch die Veränderung des globalen Zustands durch bleiebige andere teile des Codes mit hineinpsielt
    • und schwerer zu testen, da auch der globale Zustand mit einbezigen wird
    • globaler Zustand erfordert Synchronisation in einem Multithreading-Kontext und kann Engpässe schaffen, die der Skalierbarkeit schaden
    • interne Abhängigkeiten machen es schwer, Teile des Moduls herauszulösen
    • daher schlechter zu Refaktorisieren
    • und auch schlechtere Wiederverwendbarkeit: Ich kann eine Klasse nicht einfach in ein anderes Projekt kopieren ohne die globalen Variablen mitzunehmen, die eventuell in dem neuen Projekt wenig Sinn machen.
    • nicht zu vergessen: Probleme mit Initialisierungsreihenfolge und Lebenszeit.

    Das erachte ich aber nur dann als poblematsich, wenn das Modul auch tatsächlich seinen veränderlichen Zustand in den globalen Variablen verwaltet, dieser standing "im Fluss ist" und sich Teile des Programms anders verhalten je nachdem, welche Teile des Programms vorher aktiv waren.

    Unveränderlichen, oder "quasi-unveränderlichen" Zustand erachte ich hingegen als völlig in Ordnung:

    • globale Konstanten sowieso
    • aber auch globale Konfiguration, die nur einmal geladen und dann nicht mehr (oder nur extrem selten) verändert wird (muss nicht mit const einhergehen, das kann auch ein dynamsicher vector<string> sein).
    • oder eben auch solche Fehlermeldungen und Kommandos, auch wenn die de facto "dynamisch" sind und z.B. jede Klasse einmal bei Programmstart seine eigenen Einträge hinzufügt und die dann unverändert bleiben (ich nenn das mal "pseudo-konstant").

    Ein solch globaler vector<string> errormsgs kann also durchaus okay sein. Wenn du möchtest @Yadgar, kannst du uns allerdings auch mal beschreiben, wie du dir die Fehlerbehandlung vorgestellt hast, da gibt es nämlich mehrere verbreitete Ansätze (Exceptions, error codes, modernere error-code-ähnliche Ansätze wie std::expected). Vielleicht haben wir ja ein paar Tips wie man das gut umsetzen könnte. Eventuell braucht man ja diesen globalen vector ja gar nicht. Ein Fehler wie "Befehl unbekannt!" könnte z.B. nur an einer einzigen Stelle im Code auftreten, und da könnte man auch gleich eine Exception mit der Meldung werfen oder die Meldung direkt zurückgeben (z.B. auch in einem Fehler-Objekt mit std::expected).

    Und für andere (ich will dich nicht zu C++98 zurückjagen @Yadgar, lass dich nicht demotivieren, man muss das nicht alles wissen oder verwenden 😉 ):

    Einen interessanten Ansatz für Lowlevel-Fehlermeldungen der auch kompatibel mit einer nach außen exponierten C-API ist, die nur Fehlercodes zurückgeben soll finde ich so was hier, das C++26 Annotations verwendet:

    enum class ErrorCode : int
    {
        [[=annotations::message("No error")]]
        NO_ERRROR = 0,
        [[=annotations::message("Invalid command")]]
        INVALID_COMMAND = 1,
        [[=annotations::message("File not found")]]
        FILE_NOT_FOUND = 2
    };
    

    Da kann man eine compile-time-konstante Fehlermeldung direkt an einen enum-Wert hängen, den man z.B. als Fehlercode zurückgibt und bei Bedarf mit std::meta::annotations_of abfragen. Das ist derzeit noch etwas fummelig, da solche Strings Structural Types sein müssen (die auch als NTTP auftreten könnten). Sowas wie std::fixed_string soll das mal lösen, ist aber noch nicht standardisiert, daher muss mans den selbst implementieren).

    Das hat schon was, wie ich finde. Zumindest wenn man nicht mit Exceptions arbeiten kann 😉



  • @Finnegan sagte in Fehlermeldung bei globalem vector-Objekt vom Typ string:

    Ich würde das mit den "globale Variablen vermeiden" auf solche einschränken, die zu einem veränderlichen globalen Zustand eines Moduls führen. Die haben in der Tat mehrere Nachteile:

    Und clang-tidy zeigt da noch einen weiteren Nachteil. Auch Konstruktoren können Exceptions werfen. Und wenn dies beim Erzeugen einer globalen Variable geschiet, schmiert das Programm sang und klanglos beim Programmstart ab.



  • @Quiche-Lorraine sagte in Fehlermeldung bei globalem vector-Objekt vom Typ string:

    Das geht noch schicker:

    static constexpr std::array commands {
    	"-help",
    	"-e",
    	"-engine"
    };
    

    Ah. ich dachte schon dass std::array da entsprechende Deduction Guides hat, war mir aber in dem Moment nicht mehr ganz sicher. Ich Probier das oft im Code direkt aus und schaue, ob er es frisst. In diesem Fall dürfte der Compiler allerdings ein std::array<const char*, 3> erzeugen. Wenn man std::string_view haben will, muss man dem das extra verklickern. Z.B. mit:

    using namespace std::literals;
    
    static constexpr std::array commands {
    	"-help"sv,
    	"-e"sv,
    	"-engine"sv
    };
    


  • @Quiche-Lorraine sagte in Fehlermeldung bei globalem vector-Objekt vom Typ string:

    Und clang-tidy zeigt da noch einen weiteren Nachteil. Auch Konstruktoren können Exceptions werfen. Und wenn dies beim Erzeugen einer globalen Variable geschiet, schmiert das Programm sang und klanglos beim Programmstart ab.

    Gut dass du das erwähnst! Bei der DOS-Runtime in meinem Hobbyprojekt baue ich gerade diese Initialisierung um und das bringt mich darauf, dass ich das Exception Handling von libgcc tunlichst initialisieren sollte, bevor ich die globalen Konstruktoren aufrufe. Sonst wird nämlich nicht einmal std::terminate aufgerufen, sondern der Code landet irgendwo im Nirwana. Ich glaube das läuft dann in libgcc in irgendwelchen als "unreachable" markierten Code, wo die dann eine UD2-Instruktion erzeugen (invalid Opcode).

    Das gäbe dann eine CPU-Exception die mich mal wieder auf eine falsche Fährte bringt, da ein "invalid Opcode" auch ein Klassiker ist, wenn man an eine Adresse mit Datenmüll springt (kommt bei so lowlevel-Kram häufiger mal vor 😉 )


Anmelden zum Antworten