Fehlermeldung bei globalem vector-Objekt vom Typ string
-
@Yadgar sagte in Fehlermeldung bei globalem vector-Objekt vom Typ string:
#include <string.h> #include <vector> ... vector<string> errormsgs; // Liste der Fehlermeldungen errormsgs.push_back("Befehl unbekannt!"); // in parse()Gleich vorweg: Du möchtest wahrscheinlich
#include <string>und nicht#include <string.h>verwenden. Ersteres holtstd::stringherein, letzteres ist für C String-Functionen wiestrcpyund so'n Kram. Wenn das bei dir nicht zu einem Fehler geführt hat, dann ist das purer Zufall, z.B. weil ein anderer Headerstd::stringfür dich eingebunden hat.Zu deinem Problem: Du kannst keine "Statements" zwischen den Funktionen schreiben, nur Deklarationen, Definitionen und Variablen-Initialisierungen. Wenn du möchtest, dass
errmsgseinen Default-Eintrag bekommt, dann muss du den in einer Initialisierung erzeugen. z.B. so:// Liste der Fehlermeldungen vector<string> errormsgs = { "Befehl unbekannt!" };Das ist m.E. die sauberste Form für so einen simplen Default-Eintrag. Für komplizierteres kann man auch mithilfe einer Lambda-Funktion initalisieren. Damit kommt man darum herum, dass man nicht einfach "zwischen die Funktionen" irgendwelche
v.push_back()-Funtkionen aufrufen kann:// Liste der Fehlermeldungen vector<string> errormsgs = []{ vector<string> init; init.push_back("Befehl unbekannt!"); return init; }();Info: Das "()" am Ende ist wichtig, damit wird nämlich die Lambda-Funktion direkt aufgerufen und der
vectormit dem Rückgabenwert initialisiert (Copy-Konstruktion). Merke: das ist immer noch eine Initialisierung, man kann da durchaus Funktionen in dieser Form aufrufen, nur nackte Funktionsaufrufe ohne Initialisierung sind nicht erlaubt.Alternativ geht auch so was (ich finde das mit der Lambda-Funktion allerdings eleganter, weil es alles kompakt zusammenhält):
auto init_errormsgs() { vector<string> init; init.push_back("Befehl unbekannt!"); return init; } vector<string> errormsgs = init_errormsgs();Ansonsten: Wenn du nicht unbedingt dynamische Strings benötist, kannst du für solche Fehlermeldungen auch statische Strings verwenden. Eine moderne C++-Variante wäre z.B. diese hier:
#include <vector> #include <string_view> using namespace std; // Liste der Fehlermeldungen vector<string_view> errormsgs = { "Befehl unbekannt!" };Das kann man so machen, weil solche String-Literale direkt in das Programm eingebettet werden und
char*-Pointer auf diese für die gesamte Laufzeit des Programms gültig bleiben. Das ist auch insofern effizienter, da in dieser Form der Compiler denvectordirekt in der.data-Section des Programms anlegen kann und dieser dann nicht bei der Programminitialisierung konstruiert werden muss (etwas weniger Arbeit und kompakter in der Executable). Für dynamische Strings erzeugt der Compiler nämlich einen versteckten "Global Constructor", der dann beim Start des Programms automatisch vormain()aufgerufen wird, und der vielleicht so aussehen könnte:void __GLOBAL_construct_errormsgs() { errormsgs.push_back("Befehl unbekannt!") }Ein statisches Objekt hingegen, wie der
string_view-Vektor würde stattdessen direkt fertig konstruiert geladen und benötigt eine solche Funktion nicht. Das ist nur ein relativ nebesächliches Detail, aber vielleicht ganz interessant zu wissen (das ist mir nur gerade sehr präsent, weil ich in meinem derzeitigen Hobbyprojekt um jedes Byte in der Binary kämpfe
).
-
@Yadgar sagte in Fehlermeldung bei globalem vector-Objekt vom Typ string:
int main(int argc, char* argv[])
{
vector<string> commands; // Liste der Befehle in yaigat
commands.push_back("-help");
commands.push_back("-e");
commands.push_back("-engine");
commands.push_back("-p");
commands.push_back("-prompt");
commands.push_back("-pm");
commands.push_back("-promptmagic");
commands.push_back("-rs");
commands.push_back("-randomseed");
commands.push_back("-fs");
commands.push_back("-fixedseed");
commands.push_back("-m");
commands.push_back("-model");
commands.push_back("-rt");
commands.push_back("-runtime");
commands.push_back("-w");
commands.push_back("-weight");
commands.push_back("-s");
commands.push_back("-style");
commands.push_back("-a");
commands.push_back("-average");
commands.push_back("-as");
commands.push_back("-averagesequence");
commands.push_back("-c");
commands.push_back("-count");
commands.push_back("-cq");
commands.push_back("-countquotient");Mein Vorschlag wäre, dass Du das wie folgt änderst, wenn während der Laufzeit nichts mehr dazu kommt.
// N == Anzahl der Befehle in commands static constexpr std::array<std::string_view, N> commands = { "-help", "-e", "-engine", ... }; int main() {
-
@john-0 sagte in Fehlermeldung bei globalem vector-Objekt vom Typ string:
static constexpr std::array<std::string_view, N> commandsJa. Das ist noch besser.
std::vectorecht nur wenn die Einräge dynamisch sein müssen (zur Laufzeit hinzufügen oder entfernen). Auch fürerrormsgs.
-
@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 vonstb_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_IMPLEMENTATIONundSTB_IMAGE_WRITE_IMPLEMENTATIONdürfen insgesamt nur in genau einer einzigen.cpp-Datei des Projekts stehen, da es sonst zuDublicate 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.
-
@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
consteinhergehen, das kann auch ein dynamsichervector<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> errormsgskann 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 wiestd::expected). Vielleicht haben wir ja ein paar Tips wie man das gut umsetzen könnte. Eventuell braucht man ja diesen globalenvectorja 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 mitstd::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 mitstd::meta::annotations_ofabfragen. Das ist derzeit noch etwas fummelig, da solche Strings Structural Types sein müssen (die auch als NTTP auftreten könnten). Sowas wiestd::fixed_stringsoll 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::arrayda 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 einstd::array<const char*, 3>erzeugen. Wenn manstd::string_viewhaben 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
libgcctunlichst initialisieren sollte, bevor ich die globalen Konstruktoren aufrufe. Sonst wird nämlich nicht einmalstd::terminateaufgerufen, sondern der Code landet irgendwo im Nirwana. Ich glaube das läuft dann inlibgccin irgendwelchen als "unreachable" markierten Code, wo die dann eineUD2-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
)
-
Ich fürchte, es gibt dafür in C++ kein einheitliches Idiom.
Ich wäre aber geneigt, zu sagen, Fehlermeldungen sollten immer erst dort erzeugt werden, wo sie gebraucht werden.
Das hätte den Vorteil, dass keine Duplikate entstehen und Stacktracing möglich ist. Und Unit-Tests sollten ohnehin nicht auf genaue Fehlermeldungen testen (stattdessen auf den Typ der Exception, den Fehlercodeint oder auf ein contains keyword, etc.).
Außerdem gelten Fehlermeldungen afaik nicht als magische Konstanten, und wenn der User neben dem Fehlercode eine Übersetzung braucht, könnte an entsprechender Stelle ein Service aufgerufen werden, um diese Fehlermeldung zu finalisieren.