Fehlinitialisierung in C++
Die Fehlinitialisierung (default initialization ) wird bei Definitionen ohne Initialisierer (initializer ) durchgeführt.
Fehlinitialisierung bei fundamentalen Typen
main.cpp
#include <iostream> /* ::std::cout */
#include <ostream> /* << */int main(){ int a; ::std::cout << a << '\n'; }
Im obigen Programm wird ein Objekt ohne Initialisierer definiert. Dies bedeutet, daß eine Fehlinitialisierung vorgenommen wird. (Die Bezeichnung „Fehlinitialisierung“ ergibt sich daraus, daß ein Initialisierer fehlt.) Bei den Typen »int«, »double«, »bool«, und »char« bedeutet die Vornahme einer Fehlinitialisierung bei lokalen Variablen in der Regel (in Situationen wie in dem obigen Beispiel), daß keine Initialisierung vorgenommen wird: Wenn das zugehörige Objekt mit automatischer Lebensdauer nicht initialisiert wird, dann hat es einen unbestimmten Wert.
(Objekten mit statischer Lebensdauer werden grundsätzlich vor allen anderen Initialisierungen erst einmal 0-initialisiert.)
Fehlinitialisierung bei Klassenobjekten
Bei Klassenobjekten hängt die Zulässigkeit und die Bedeutung der Fehlinitialisierung vom Typ des Objektes ab, also von seiner Klasse. Im Falle der Klasse »::std::string« wird eine leere Zeichenfolge initialisiert. In der Regel kann man davon ausgehen, daß ein Klassenobjekt durch eine Fehlinitialisierung in einen zulässigen „leeren“ oder „neutralen“ Zustand gebracht wird. Es kann aber auch sein, daß eine Klasse solch eine Initialisierung gar nicht gestattet. In diesem Fall erhält man dann eine Fehlermeldung.
main.cpp
#include <iostream> /* ::std::cout */
#include <ostream> /* << */
#include <string> /* ::std::string */
using namespace ::std::literals;int main() { ::std::string const s; ::std::cout << '>' << s << "<\n"s; }
::std::cout
><
Bei fundamentalen Objekten ist eine Fehlinitialisierung keine Initialisierung, bei Klassenobjekten hängt ihre Bedeutung von der Klasse ab.
Der Konstruktor für die Fehlinitialisierung
Der Konstruktor für die Fehlinitialisierung findet sich in der Dokumentation der Klasse als ein Konstruktor, der mit leeren Klammern aufgerufen werden könnte, falls er eine normale Funktion wäre. Wir nennen solch einen Konstruktor mit leeren Klammern auch Fehlkonstruktor. Wir finden ihn in der im folgenden auszugsweise wiedergegebenen Dokumentation der Klasse »::std::string« ganz am Anfang.
- Dokumentation der Klasse »::std::string« nach C++ 2015, 21.4.2 (vereinfacht und übersetzt)
::std::string();
- Anlegen eines leeren ::std::string-Exemplars.
::std::string …( ::std::initializer_list< char >l );
- Anlegen eines ::std::string-Exemplars mit dem Text der Zeichen einer Initialisierungsliste
::std::string( ::std::string const & s );
- Anlegen eines ::std::string-Exemplars mit dem Text eines anderen ::std::string-Exemplars »s«.
::std::string( char const * s );
- Anlegen eines ::std::string-Exemplars mit dem Text einer Zeichenfolge »s«.
::std::string( int n, char c );
- Anlegen eines ::std::string-Exemplars mit dem Text einer n-fachen Wiederholung des Zeichens »c«.
Manche Klassen haben keinen Fehlkonstruktor. In diesem Fall kann man ihre Objekt dann auch nicht fehlinitialisieren.
Es kann auch sein, daß andere Konstruktoren als der Konstruktor mit leeren Klammern für eine Fehlinitialisierung herangezogen werden. Sie kommen dann dafür in Frage, wenn sie zwar Parameter haben, aber bei einer Funktion mit denselben Parameterdeklarationen auch ein Aufruf ohne Argument erlaubt wäre.
Die Deklaration von Funktionen
Das folgende Programm zeigt einen Versuch eines Programmierers, ein Exemplar der Klasse »::std::string« mit einer Fehlinitialisierung anzulegen. In Analogie zur Direktinitialisierung mit »::std::string const s( "abc"s );« verwendet der Programmierer dazu die Schreibweise »::std::string const s();«, die eine Direktinitialisierung mit leeren runden Klammern werden soll und daher den entsprechenden Konstruktor mit leeren Klammern verwenden soll.
main.cpp
#include <iostream> /* ::std::cout */
#include <ostream> /* << */
#include <string> /* ::std::string */using namespace ::std::literals;
::std::string s() { return ""s; }
int main() { ::std::string const s(); ::std::cout << '>' << s << "<\n"s; }
::std::cout
>1<
In C++ wird diese Schreibweise aber als Deklaration einer Funktion »s« interpretiert.
Immer, wenn ein Quelltext sowohl als Funktionsdeklaration als auch als Variablendeklaration interpretiert werden kann, hat die Interpretation als Funktionsdeklaration den Vorrang.
Es ist also nicht möglich, eine Fehlinitialisierung mit leeren runden Klammern zu schreiben.
Die richtige Schreibweise zur Fehlinitialisierung ist also nicht »::std::string const s();«, sondern »::std::string const s;«.
(Das folgende Zitat begründet, warum die Funktion wohl auch definiert sein muß.)
- Zitat *
- “Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required.”
Die Adressen von Funktionen werden bei der verwendeten C++ -Implementation stets nur symbolisch als »1« ausgegeben (nach Wandlung in den Typ »bool«), was die oben zu sehende Ausgabe erklärt.
main.cpp
#include <iostream> /* ::std::cout */
#include <ostream> /* << */
#include <string> /* ::std::string */using namespace ::std::literals;
::std::string s() { return ""s; }
int main()
{ ::std::string const s();
bool const b = s;
::std::cout << '>' << b << "<\n"s; }::std::cout
>1<
Die Fehlinitialisierung mit geschweiften Klammern
Eine Fehlinitialisierung mit leeren Klammern ist aber möglich, wenn dabei geschweifte Klammern verwendet werden.
C++ garantiert, daß bei einer Initialisierung mit leeren geschweiften Klammern immer der Fehlkonstruktor aktiviert wird.
main.cpp
#include <iostream> /* ::std::cout */
#include <ostream> /* << */
#include <string> /* ::std::string */using namespace ::std::literals;
int main() { ::std::string const s{}; ::std::cout << '>' << s << "<\n"s; }
::std::cout
><
Die Tatsache, daß bei Verwendung geschweifter Klammern keine Verwechslung mit einer Funktionsdeklaration möglich ist, erklärt, warum oft empfohlen wird, die Schreibweise mit geschweiften Klammern falls möglich zu bevorzugen.
Die Fehlinitialisierung mit runden und geschweiften Klammern
Falls ein Konstruktor, dessen Parameter eine Initialisierungsliste ist, mit einer leeren Initialisierungsliste aktiviert werden soll, so ist dafür die Schreibweise (bei einer Variablen »s«) nicht etwa die Schreibweise »s{}«, sondern die Schreibweise »s( {} )« zu verwenden.
Das folgende Programm verwendet nun also den Konstruktor, dessen Parameter den Typ »::std::initializer_list< char >« hat, seine Ausgabe unterscheidet sich aber nicht von dem vorigen Programm, das den Fehlkonstruktor aktivierte. Bei Verwendung anderer Klassen als der Klasse »::std::string« könnte es jedoch einen Unterschied geben.
main.cpp
#include <iostream> /* ::std::cout */
#include <ostream> /* << */
#include <string> /* ::std::string */using namespace ::std::literals;
int main() { ::std::string const s( {} ); ::std::cout << '>' << s << "<\n"s; }
::std::cout
><
Die Fehlinitialisierung mit Gleichheitszeichen und geschweiften Klammern
“main.cpp
#include <iostream> /* ::std::cout */
#include <ostream> /* << */
#include <string> /* ::std::string */using namespace ::std::literals;
int main() { ::std::string const s = {}; ::std::cout << '>' << s << "<\n"s; }
::std::cout
><
Im voranstehenden Programm sehen wir die Kopier-Listen-Initialisierung mit der Schreibweise »s = {}«.
(Die Schreibweise »s{}« wäre hingegen eine Direkt-Listen-Initialisierung .)
Bei dieser Kopier-Listen-Initialisierung mit leeren geschweiften Klammern wird ebenfalls ein Konstruktor aktiviert, dessen Parameterliste bei einer Funktion zu einem Aufruf mit leeren Klammern verträglich wäre.
Im Gegensatz zur Direkt-Listen-Initialisierung werden bei der Kopier-Listen-Initialisierung jedoch nur Konstruktoren herangezogen, die nicht mit »explicit« gekennzeichnet sind.