Forum: PC-Programmierung String-Konstanten in C++


von Dennis S. (eltio)


Lesenswert?

Hallo zusammen,
Ziel ist es String-Konstanten in einem C++-Programm an einer zentralen 
Stelle auszulagern.

----------------

Beispiel: In C würde ich (mit char) MISRA-konform so prinzipiell 
folgendes machen:

msg.h
1
extern char* foo;
2
extern char* bar;

msg.c
1
const char* foo = "Hallo Welt!";
2
const char* bar = "Hello World!";

main.c
1
#include "msg.h"

----------------

Möglich wäre prinzipiell auch in C++:

msg.hpp
1
#include <string>
2
const string foo = "Hallo Welt!";
3
const string bar = "Hello World!";

main.cpp
1
#include "msg.hpp"

Mir gefällt es nicht Header in Header zu inkludieren. Außerdem sollte ja 
vermutlich keine Definition im Header sein.

Was ist "Best-Practice"?

Gruß

von Dirk B. (dirkb2)


Lesenswert?

Dennis S. schrieb:
> Mir gefällt es nicht Header in Header zu inkludieren.

Warum nicht?
Im Header muss alles enthalten sein, damit du ihn benutzen kannst.
Wenn das schon in einem anderen Header steht, musst du ihn einbinden.
Dafür hat sich irgendwann mal jemand die Include-Guards ausgedacht.

von Dr. Sommer (Gast)


Lesenswert?

Dennis S. schrieb:
> Mir gefällt es nicht Header in Header zu inkludieren

Dann solltest du kein C oder C++ benutzen, da macht man das nämlich 
ständig weil es ohne kaum geht.

In C++ geht es genau wie in C, schreib "extern const std::string foo;" 
in den Header, und 'const std::string foo ("asdf");' in die Source 
Datei.

Sauberer wäre es natürlich wenn die Konstante nicht global ist. Warum 
brauchst du die an so vielen Stellen?

Und wenn du schon pingelig bist (MISRA), schreibe nicht "using namespace 
std;" sondern schreibe das std:: aus. Siehe:

https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice

von Dennis S. (eltio)


Lesenswert?

Danke für eure Antworten.

Dirk B. schrieb:
> Warum nicht?
Eine nicht näher spezifizierbare Ahnung im Hinterkopf, gelesen zu haben, 
genau dies zu vermeiden.

Dr. Sommer schrieb:
> Dann solltest du kein C oder C++ benutzen, da macht man das nämlich
> ständig weil es ohne kaum geht.
Naja.. Gründe dagegen gibt es ja (beispielsweise längere Compilierzeit). 
Nun gilt es das kaum aus deiner Antwort genauer zu beleuchten.

> Sauberer wäre es natürlich wenn die Konstante nicht global ist. Warum
> brauchst du die an so vielen Stellen?
Ich brauche die nicht "an so vielen Stellen", sondern möchte sie aus 
Gründen der Übersichtlichkeit und Wartbarkeit (z.B.: Übersetzungen), an 
einer Stelle haben. Auch wenn hier nicht wichtig: es handelt sich um 
Ausgabemeldungen für den User.

> Und wenn du schon pingelig bist (MISRA), schreibe nicht "using namespace
> std;" sondern schreibe das std:: aus. Siehe:
Das ist jetzt aber sehr am Thema vorbei wenn du ehrlich bist...

Gruß

von Dennis S. (eltio)


Lesenswert?

Dirk B. schrieb:
> Im Header muss alles enthalten sein, damit du ihn benutzen kannst.

Habe ich jetzt an einer anderen Stelle auch so gelesen.. scheint also 
nicht ganz fern der Praxis zu sein. ;-)

Gruß

von Dirk B. (dirkb2)


Lesenswert?

Dennis S. schrieb:
> Eine nicht näher spezifizierbare Ahnung im Hinterkopf, gelesen zu haben,
> genau dies zu vermeiden.

Du sollst nicht blind alle möglichen Header auf Vorrat einbinden, 
sondern nur die benötigten.

Dennis S. schrieb:
> Naja.. Gründe dagegen gibt es ja (beispielsweise längere Compilierzeit).

Die Header werden spätestens im Code gebraucht. Also müssen sie (früher 
oder später) eingebunden werden.
Es gibt auch noch
1
#pragma once

von Dr. Sommer (Gast)


Lesenswert?

Dennis S. schrieb:
> Eine nicht näher spezifizierbare Ahnung im Hinterkopf, gelesen zu haben,
> genau dies zu vermeiden.

Keine gute Quelle ;-)

Dennis S. schrieb:
> Naja.. Gründe dagegen gibt es ja (beispielsweise längere Compilierzeit).
> Nun gilt es das kaum aus deiner Antwort genauer zu beleuchten.

In Headern keine Includes zu benutzen würde ja bedeuten, dass sich alle 
Programmstrukturen nur aus fundamentalen Typen zusammensetzen. Das würde 
eine ziemlich magere Struktur bewirken. Auf unnötige Inkludes sollte man 
aber schon verzichten. Die Compilier Zeit leider darunter auch nicht so 
dramatisch; Metaprogrammierung ist da z.B. viel schlimmer.

Dennis S. schrieb:
> Auch wenn hier nicht wichtig: es handelt sich um Ausgabemeldungen für
> den User.

Wie wär's, die in eine Klasse zu packen statt sie global zu machen?

Dennis S. schrieb:
> Das ist jetzt aber sehr am Thema vorbei wenn du ehrlich bist...

Da du offensichtlich Anfänger bist hatte ich vermutet, dass du dich über 
Hinweise auf weitere Probleme freuen würdest.

von Dr. Sommer (Gast)


Lesenswert?

PS: wenn Compile-Zeit so wichtig ist nimm Java, das ist da viel 
schneller als C oder C++ und hat außerdem gar keine Header 
Problematik...

von Dennis S. (eltio)


Lesenswert?

Dr. Sommer schrieb:
> Keine gute Quelle ;-)
Dem schließe ich mich an. :-D

Dr. Sommer schrieb:
> In Headern keine Includes zu benutzen würde ja bedeuten, dass sich alle
> Programmstrukturen nur aus fundamentalen Typen zusammensetzen.
Das ist eine sehr einleuchtende Begründung.. vielen Dank.

Dr. Sommer schrieb:
> Wie wär's, die in eine Klasse zu packen statt sie global zu machen?
Das wäre auch denkbar. Ich probiere beide Wege aus!

Dr. Sommer schrieb:
> Da du offensichtlich Anfänger bist hatte ich vermutet, dass du dich über
> Hinweise auf weitere Probleme freuen würdest.
Das ist natürlich so, entschuldige. Ich wollte nur (vielleicht etwas zu 
vehement) verhindern, dass der Thread, wie hier schon oft erlebt, völlig 
aus dem Ruder läuft.

Gruß

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Dennis S. schrieb:
> Möglich wäre prinzipiell auch in C++:

Und auch in C++ ist es weithin möglich, String Literale zu verwenden. 
Wenn Du die Konstanten nicht als std::string brauchst, bzw. später nicht 
andauernd dorthin konvertieren musst, kannst Du Dir überlegen, auch 
weiterhin String Literale zu verwenden (insbsondere, wenn es eine 
embedded Anwendung ist).

Die Nutzung einer "zentralen" Stelle, mag irgend wie intuitiv sein, wird 
aber sehr schnell unübersichtlich, wenn ein Projekt größer wird.

Ansonsten:

msg.hpp
1
#ifndef MSG_H
2
#define MSG_H
3
#include <string>
4
5
extern const std::string foo;
6
extern const std::string bar;
7
8
#endif

msg.cpp
1
#include "msg.h"
2
3
const std::string foo = "Hallo Welt!";
4
const std::string bar = "Hello World!";

> Was ist "Best-Practice"?

- Symbole so local wie möglich zu halten. Evtl. auch darauf verzichten, 
für die Konstante "Es ist gerade ein Fehler aufgetreten, möchten Sie die 
Foobar wiederholen?" ein eigenes Symbol mit dem Namen ERROR_232 oder 
inform_the_user_about_an_error_and_ask_if_he_wants_to_retry_foobar 
anzulegen.
- Auf Lesbarkeit achten und dabei den Kopf einschalten.
- Niemals auf Regeln hören, in denen die Wörter "Niemals", "immer" oder 
"sauber" vorkommen!

mfg Torsten

von Programmiersprachentheaterintendant (Gast)


Lesenswert?

>> Naja.. Gründe dagegen gibt es ja (beispielsweise längere Compilierzeit).

Aha. Da nimmt man Compilers welche in jeder Versionsiteration den 
Quellcode mehr und besser auf NoNos abklopfen können, gönnt ihnen aber 
das Mehr an Zeit dies zu tun nicht? Auf immer schneller werdenden 
Entwicklungsworkstations?

Wenn Build Time zum Zeitfresser wird, ist es höchste Zeit sich die 
Makefiles (oder was stattdessen eingesetzt wird) vorzuknöpfen und die 
Abhängigkeitsregeln an den zu übersetzenden Quellcode 
anzupassen/richtigzustellen. Auch die im eigenen Quellcode gewählte 
Verteilung der Dinge ist da oft ein Problem.
BTDT und Faktor 5++ rausgeholt: plötzlich greift nämlich die 
Parallelisierbarkeit make -j


> - Niemals auf Regeln hören, in denen die Wörter "Niemals", "immer" oder "sauber" 
vorkommen!

Schau an: eine solche Regel...   %-}

von Alles Humbug (Gast)


Lesenswert?

Torsten R. schrieb:
> - Niemals auf Regeln hören, in denen die Wörter "Niemals", "immer" oder
> "sauber" vorkommen!

Also auf sowas hier schon mal nicht hören:

"Dr. Sommer (Gast)
Sauberer wäre es natürlich wenn die Konstante nicht global ist."

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Programmiersprachentheaterintendant schrieb:

> Wenn Build Time zum Zeitfresser wird, ist es höchste Zeit sich die
> Makefiles (oder was stattdessen eingesetzt wird) vorzuknöpfen und die
> Abhängigkeitsregeln an den zu übersetzenden Quellcode
> anzupassen/richtigzustellen.

Die allermeisten unter C++ verwendeten Build-System sollten diese 
Abhängigkeiten für das Übersetzen aus dem Quellcode selbst ermitteln 
können.

Den Tipp, auf überflüssige includes zu versichten, ist aber sicher 
korrekt und hilfreich. Die Verwendung von "zentrallen Stellen" führt in 
den meisten Fällen eher zu headern, die letzendlich in jeder translation 
unit landen und damit den build verlangsamen.

von Vlad T. (vlad_tepesch)


Lesenswert?

Dennis S. schrieb:
> Möglich wäre prinzipiell auch in C++:
>
> msg.hpp
> #include <string>
> const string foo = "Hallo Welt!";
> const string bar = "Hello World!";
>
> main.cpp
> #include "msg.hpp"
>

sollte dies nicht bei mehrfacher includierung doppelte Symbole im Linker 
geben?

: Bearbeitet durch User
von Dr. Sommer (Gast)


Lesenswert?

Vlad T. schrieb:
> sollte dies nicht bei mehrfacher includierung doppelte Symbole im Linker
> geben?
Ja, es fehlt das "extern".

von Dennis S. (eltio)


Lesenswert?

Vlad T. schrieb:
> sollte dies nicht bei mehrfacher includierung doppelte Symbole im Linker
> geben?
Doch nicht bei der Verwendung von Include-Guards?

Dr. Sommer schrieb:
> Ja, es fehlt das "extern".
Das würde nicht funktionieren, weil in der Header-Datei dann keine 
Definition mehr vorhanden wäre?!

Gruß

von Dr. Sommer (Gast)


Lesenswert?

Dennis S. schrieb:
> Doch nicht bei der Verwendung von Include-Guards?

Doch, die sind Wirkungslos wenn der selbe Header von mehreren 
.cpp-Dateien (Translation Units) inkludiert wird.

Dennis S. schrieb:
> Das würde nicht funktionieren, weil in der Header-Datei dann keine
> Definition mehr vorhanden wäre?!
Die Definition sollte man dann natürlich in die main.cpp packen. 
Definitionen von Variablen in Headern gehen nur, wenn der Header von 
genau einer .cpp-Datei inkludiert wird. Da das eher unpraktikabel ist, 
sollte man es lieber gar nicht tun.

von Dennis S. (eltio)


Lesenswert?

Programmiersprachentheaterintendant schrieb:
> Auf immer schneller werdenden Entwicklungsworkstations?
Tja.. ich warte noch auf den Rechner, der automatisch mit jeder 
Software-Version des Compilers schneller wird.. ;-)

Davon abgesehen: "Ignorier einfach potentielle Probleme (== binde 
einfach alle Header-Dateien ein) und lass ein neues Tool das machen (== 
nutze Make / CMake)" halte ich für eine kurzsichtige Heransgehensweise.

Gruß

von Mikro 7. (mikro77)


Lesenswert?

Vlad T. schrieb:
>> #include <string>
>> const string foo = "Hallo Welt!";
>> const string bar = "Hello World!";
>
> sollte dies nicht bei mehrfacher includierung doppelte Symbole im Linker
> geben?

Hatte ich auch gedacht. Der Linker scheint allerdings die identischen 
Symbole zu identifizieren. Solange sie const sind, wird gelinkt. Sind 
sie nicht const, gibt es eine Fehlermeldung (gcc 5.4.0).

von mh (Gast)


Lesenswert?

Mikro 7. schrieb:
> Vlad T. schrieb:
>>> #include <string>
>>> const string foo = "Hallo Welt!";
>>> const string bar = "Hello World!";
>>
>> sollte dies nicht bei mehrfacher includierung doppelte Symbole im Linker
>> geben?
>
> Hatte ich auch gedacht. Der Linker scheint allerdings die identischen
> Symbole zu identifizieren. Solange sie const sind, wird gelinkt. Sind
> sie nicht const, gibt es eine Fehlermeldung (gcc 5.4.0).

Dazu den letzte Satz in folgendem Textausschnitt beachten:

aus http://en.cppreference.com/w/cpp/language/definition
1
One Definition Rule
2
3
Only one definition of any variable, function, class type, enumeration type, concept (since C++20) or template is allowed in any one translation unit (some of these may have multiple declarations, but only one definition is allowed).
4
5
One and only one definition of every non-inline function or variable that is odr-used (see below) is required to appear in the entire program (including any standard and user-defined libraries). The compiler is not required to diagnose this violation, but the behavior of the program that violates it is undefined.
6
...

In diesem konkreten Fall ist auch das odr-used zu beachten, da foo und 
bar in dem gezeigten Ausschnitt nicht benutzt werden.

von Mikro 7. (mikro77)


Lesenswert?

mh schrieb:
>
1
> One and only one definition of every non-inline function or variable 
2
> that is odr-used (see below) is required to appear in the entire program 
3
> (including any standard and user-defined libraries). The compiler is not 
4
> required to diagnose this violation, but the behavior of the program 
5
> that violates it is undefined.
6
> ...
7
>
>
> In diesem konkreten Fall ist auch das odr-used zu beachten, da foo und
> bar in dem gezeigten Ausschnitt nicht benutzt werden.

Unter welchen Umständen könnte das o.g. const foo odr-used werden?

von mh (Gast)


Lesenswert?

Mikro 7. schrieb:
> Unter welchen Umständen könnte das o.g. const foo odr-used werden?

Der Link in meinem Post enthält auch eine Erklärung zu odr-used. Ich 
kann verstehen, wenn man den "Formally" Teil nicht versteht. Aber der 
Abschnitt fängt mit "Informally" an und ist ziemlich einfach:
1
ODR-use
2
Informally, an object is odr-used if its value is read (unless it is a compile time constant) or written, its address is taken, or a reference is bound to it; a reference is odr-used if ...

Also als konkrete, aber nicht vollständige, Antwort auf deine Frage: 
Wenn im Programm die Adresse von foo oder bar benutzt wird, oder eine 
Referenz auf eins der Objekte zeigt.

von Mikro 7. (mikro77)


Lesenswert?

Wie oben geschrieben: gcc akzeptiert den std::string mehrfach (wenn er 
const ist). Konkret wird er im Executable bspw. zweimal instantiiert. 
Nämlich einmal für jeden der Zugriffe aus den unterschiedlichen 
Compilation Units.

Ich bin mir jetzt aber nicht ganz klar darüber was du mitteilen 
möchtest.

von tictactoe (Gast)


Lesenswert?

Mikro 7. schrieb:
> Wie oben geschrieben: gcc akzeptiert den std::string mehrfach (wenn er
> const ist). Konkret wird er im Executable bspw. zweimal instantiiert.
> Nämlich einmal für jeden der Zugriffe aus den unterschiedlichen
> Compilation Units.

Das muss so sein. Globale const-Variablen sind automatisch auch 
"internal linkage", d.h. als wären sie static deklariert. Dem kann man 
durch eine zusätzliche Deklaration mit extern entgegenwirken, wenn 
notwendig.

von Eric B. (beric)


Lesenswert?

Torsten R. schrieb:
> - Symbole so local wie möglich zu halten. Evtl. auch darauf verzichten,
> für die Konstante "Es ist gerade ein Fehler aufgetreten, möchten Sie die
> Foobar wiederholen?" ein eigenes Symbol mit dem Namen ERROR_232 oder
> inform_the_user_about_an_error_and_ask_if_he_wants_to_retry_foobar
> anzulegen.

Grundsätzlich gebe ich dir Recht, dass man Symbole so lokal wie möglich 
halten soll, aber gerade bei Text-Konstanten macht es durchaus Sinn die 
alle zentral zu verwalten, damit es z.B. einfacher wird das Programm in 
verschiedene Sprachen zu übersetzen.
Dann würde ich aber die Texte in z.B. eine Excel Tabelle verwalten und 
mir daraus C/C++ Code generieren lassen.

> - Niemals auf Regeln hören, in denen die Wörter "Niemals", "immer" oder
> "sauber" vorkommen!

Immer doch! :-D

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.