Direktinitialisierungen in C++ [] (Direktinitialisierungen in C++ ), Lektion, Seite 722282
https://www.purl.org/stefan_ram/pub/direktinitialisierungen_c++ (Permalink) ist die kanonische URI dieser Seite.
Stefan Ram
C++-Kurs

Direktinitialisierung in C++ 

Eine Initialisierung mit der Schreibweise

T  x { a  };

(…) wird Direktinitialisierung  genannt. (Hierbei steht „T “ für einen Typ, „x “ für einen Namen und „a “ für einen Ausdruck).

Das folgende Programm zeigt eine Direktinitialisierung einer Variablen eines fundamentalen Typs, wie sie schon im Grundkurs behandelt wurde. Das Objekt »a« wird mit dem Wert »7« initialisiert.

main.cpp

#include <iostream> /* ::std::cout */
#include <ostream> /* << */
#include <initializer_list>

int main() { int const a{ 7 }; ::std::cout << a << '\n'; }

::std::cout
7

Die Direktinitialisierung haben wir auch schon in früheren Lektionen dieses Aufbaukurses verwendet, das Thema wird nun hier etwas vertieft.

Klassen als Typen

Es ist auch möglich, Variablen anzulegen, deren Typ eine Klasse  ist. Wir zeigen dies hier an Hand der Klasse »::std::string«.

main.cpp

#include <iostream> /* ::std::cout */
#include <ostream> /* << */
#include <string> /* ::std::string */

using namespace ::std::literals;

int main()
{ ::std::string const a{ "alpha"s }; ::std::cout << a << '\n'; }

::std::cout
alpha

Auch ein Programm wie dieses wurde schon im Grundkurs behandelt.

Bei der Direktinitialisierung einer Variablen vom Typ »::std::string« wird ein Objekt der Klasse »::std::string« gemäß den Angaben in den Klammern erzeugt.

Die Direktinitialisierung ähnelt einem direkten Aufruf der Konstruktoren, dabei wird eine Überladungsauflösung wie bei Funktionsaufrufen herangezogen, und bei Bedarf werden Argumente gewandelt.

Überladungsauflösung  bedeutet, daß an Hand der Anzahl und des Typs der Ausdrücke (Argumente) in den Klammern, der am besten zu den Argumenten passende Konstruktor herangezogen wird.

Anmerkungen

Das »const« ist in dem obigen Programm nicht unbedingt nötig, es drückt hier nur aus, daß das Exemplar nicht verändert werden soll.

Wenn der Name »::std::string« oder bestimmte anderen Namen, die mit der Klasse »::std::string« zu tun haben, in einem Programm verwendet werden, dann ist es nötig, am Anfang jenes Programms die Direktive »#include <string>« zu verwenden.

Daß »::std::string« der Name einer Klasse ist und welche Bedeutung der in den Klammern angegebenen Wertes für das erzeugte Exemplar hat, kann der Dokumentation der Standardbibliothek entnommen werden.

Runde Klammern

Bei der Initialisierung können auch runde Klammern  an Stelle von geschweiften Klammern verwendet werden.

Auch die Initialisierung mit einem Wert in einer Initialisierungsliste mit runden Klammern  ist eine Direktinitialisierung.

main.cpp

#include <iostream> /* ::std::cout */
#include <ostream> /* << */

int main() { int const a( 7 ); ::std::cout << a << '\n'; }

::std::cout
7
main.cpp

#include <iostream> /* ::std::cout */
#include <ostream> /* << */
#include <string> /* ::std::string */

using namespace ::std::literals;

int main()
{ ::std::string const a( "alpha"s ); ::std::cout << a << '\n'; }

::std::cout
alpha

In den hier gezeigten Fällen ist es egal, welche Arten von Klammern verwendet werden. Später werden wir Fälle kennenlernen, in denen die Wahl einer Art von Klammern eine Auswirkung hat.

Eine Initialisierung in einer der beiden Schreibweisen

T  x ( a  );

T  x { a  };

(…) wird Direktinitialisierung  genannt. (Hierbei steht „T “ für einen Typ, „x “ für einen Namen und „a “ für einen Ausdruck.)

Die Klammer (wie »( a  )« oder »{ a  }« oben) ist hierbei ein Initialisierer.

Kopierinitialisierung mit runden Klammern

In dem folgenden Programm wurde der Initialisierungsausdruck »a« eingeklammert. Dies ändert nichts daran, daß es sich weiterhin um eine Kopierinitialisierung  handelt. Es stellt keine weitere Form der Initialisierung dar und darf nicht mit der eben vorgestellten Direktinitialisierung verwechselt werden, bei welcher kein  Gleichheitszeichen verwendet wird.

main.cpp

#include <iostream> /* ::std::cout */
#include <ostream> /* << */
#include <string> /* ::std::string */

using namespace ::std::literals;

int main()
{ ::std::string const a( "alpha"s );
::std::string const b =( a );
::std::cout << b << '\n'; }

::std::cout
alpha

Direktinitialisierung mit Kopierkonstruktor

Umgekehrt handelt sich im folgenden um Direktinitialisierungen, auch wenn dabei ein Objekt mit dem Kopierkonstruktor kopiert wird. Eine „Kopierinitialisierung“ liegt vor, wenn der Initialisierer mit einem Gleichheitszeichen beginnt, und nicht unbedingt, wenn der Kopierkonstruktor herangezogen wird. Umgekehrt ist es nicht ausgeschlossen, daß bei einer Direktinitialisierung der Kopierkonstruktor herangezogen wird. Der Kopierkonstruktor ist also nicht direkt mit der Kopierinitialisierung verbunden!

main.cpp

#include <iostream> /* ::std::cout */
#include <ostream> /* << */
#include <string> /* ::std::string */

using namespace ::std::literals;

int main()
{ ::std::string const a( "alpha"s );
{ ::std::string const b( a ); ::std::cout << b << '\n'; }
{ ::std::string const b{ a }; ::std::cout << b << '\n'; }}

transcript
alpha
alpha

In beiden inneren Blöcken wird »a« mit dem Kopierkonstruktor  kopiert, aber es handelt sich trotzdem nicht um Kopierinitialisierungen, weil kein Gleichheitszeichen verwendet wird. Es handelt sich um Direktinitialisierungen, für die allerdings der Kopierkonstruktor  herangezogen wird.

Kopierinitialisierung und Direktinitialisierung im Vergleich

Eine Initialisierung in einer der beiden Schreibweisen

T  x ( a  );

T  x { a  };

(…) wird Direktinitialisierung  genannt.

Eine Initialisierung in der Schreibweisen

T  x  = a ;

(…) wird Kopierinitialisierung  genannt.

(Hierbei steht „T “ für einen Typ, „x “ für einen Namen und „a “ für einen Ausdruck)

Falls der Typ des zu initialisierende Objektes ein fundamentaler Typ, wie »int« oder »double« ist, gibt es keinen Unterschied zwischen Direkt- und Kopierinitialisierung. Wir nehmen im Rest dieses Abschnitts an, daß der Typ des zu initialisierende Objektes eine Klasse ist.

Bei einer Direktinitialisierung  wird ein Objekt unter Heranziehung eines einzigen Konstruktors  initialisiert.

Bei einer Kopierinitialisierung  wird ein Objekt unter Heranziehung des Kopierkonstruktors  initialisiert, falls der Initialisierungsausdruck nicht den dazu passenden Typ hat, wird er zuvor in den Typ des zu initialisierende Objektes umgewandelt.

Bei einer Direktinitialisierung kann  auch manchmal der Kopierkonstruktor herangezogen werden, bei einer Kopierinitialisierung wird der Kopierkonstruktor immer  herangezogen (und zuvor manchmal noch gewandelt).

Man kann sich vorstellen, daß man die Direktinitialisierung verwendet, um einen bestimmten Konstruktor gezielt heranzuziehen, während man die Kopierinitialisierung verwendet, um einen Wert in ein Objekt einer Klasse zu schreiben, nachdem der Wert gegebenenfalls entsprechend umgewandelt wurde, wozu neben Konstruktoren dann auch andere Umwandlungsmöglichkeiten herangezogen werden könnten.

Wenn der Typ des Initialisierungsausdrucks dem Typ des zu initialisierende Objektes gleich ist, gibt es keinen Unterschied zwischen beiden Initialisierungsarten.

Nicht-kopierbare Zustände

Einiger Klassen, wie beispielsweise »::std::atomic«, erlauben es nicht, ihren Kopierkonstruktors heranzuziehen, weil das Kopieren ihrer Objekte zu Störungen führen könnte.

Deswegen können Objekte solcher Klassen nur mit einer Direktinitialisierung  initialisiert werden, aber nicht mit einer Kopierinitialisierung. Bei einer Kopierinitialisierung  wird nämlich immer  der Kopierkonstruktor herangezogen (nach einer eventuell vorangehenden Typwandlung), während eine Direktinitialisierung auch ohne Heranziehung des Kopierkonstruktors verwendet werden kann.

main.cpp

#include <atomic> /* ::std::atomic */
#include <initializer_list>

int main()
{ { ::std::atomic<int> a{ 0 }; }
{ ::std::atomic<int> a( 0 ); }
{ /* ::std::atomic<int> a = 0; */ }}

Wenn ::std::atomic<int>-Objekte kopierbar wären, dann würde die »0« im Kommentar zunächst in ein ::std::atomic<int>-Objekt gewandelt werden, und jenes Objekt würde alsdann in das Objekt »a« kopiert werden.

Optimierung bei der Kopierinitialisierung

Da die Kopierinitialisierung zwei Schritte (Wandlung und Konstruktion) verwendet, während die Direktinitialisierung nur einen Schritt umfaßt (Konstruktion), könnte man denken, daß die Kopierinitialisierung langsamer  sei als die Direktinitialisierung. Wegen diverser Optimierungen der C++ -Implementation muß dies aber nicht so sein. Um eine Kopierinitialisierung zu beschleunigen darf eine C++ -Implementation die Kopierinitialisierung „hinter den Kulissen“ auch durch eine entsprechende Direktinitialisierung realisieren.

Trotzdem wird auch im Falle einer solche Umsetzung der Kopierinitialisierung, bei der nicht wirklich  kopiert wird, geprüft ob das Kopieren mit dem Kopierkonstruktor erlaubt wäre. Falls nicht, so führt dies zu einer Fehlermeldung, auch wenn tatsächlich gar nicht mit dem Kopierkonstruktor kopiert werden würde. Ein Objekt muß also für eine Kopierinitialisierung mit dem Kopierkonstruktor kopierbar sein, auch wenn der Kopierkonstruktor eventuell bei einer Realisierung der Kopierinitialisierung nicht wirklich aktiviert wird.

Übungsfragen

?   Initialisierungsart

Nennen Sie alle Variablen in dem folgenden Programm, die darin direkt-initialisiert werden!

main.cpp

#include <iostream>
#include <ostream>

int main()
{ int a{ 2 };
int b( 3 );
int c = 8;
::std::cout << a << '\n';
::std::cout << b << '\n';
::std::cout << c << '\n'; }

Zitate *

Zitat (gekürzt und umformatiert), C++  2015 8.5p16
The initialization that occurs in the forms

T x( a );

T x{ a };

(…) is called direct-initialization.
C++ 2015 8.5p15 (umformatiert)
The initialization that occurs in the
=
form of a brace-or-equal-initializer or condition (6.4), as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1), is called copy-initialization. [ Note: Copy-initialization may invoke a move (12.8). —end note ]

Seiteninformationen und Impressum   |   Mitteilungsformular  |   "ram@zedat.fu-berlin.de" (ohne die Anführungszeichen) ist die Netzpostadresse von Stefan Ram.   |   Eine Verbindung zur Stefan-Ram-Startseite befindet sich oben auf dieser Seite hinter dem Text "Stefan Ram".)  |   Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram. Schlüsselwörter zu dieser Seite/relevant keywords describing this page: Stefan Ram Berlin slrprd slrprd stefanramberlin spellched stefanram722282 stefan_ram:722282 Direktinitialisierungen in C++ Stefan Ram, Berlin, and, or, near, uni, online, slrprd, slrprdqxx, slrprddoc, slrprd722282, slrprddef722282, PbclevtugFgrsnaEnz Erklärung, Beschreibung, Info, Information, Hinweis,

Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram.
https://www.purl.org/stefan_ram/pub/direktinitialisierungen_c++