Explizite Konstruktordefinitionen in C++ (Explizite Konstruktordefinitionen in C++), Lektion, Seite 723724
https://www.purl.org/stefan_ram/pub/explizite_konstruktoren_c++ (Permalink) ist die kanonische URI dieser Seite.
Stefan Ram
C++-Kurs

Benutzerdefinierte Konstruktordefinitionen in C++ 

Undefiniertes Verhalten ohne Initialisierung

In dem folgenden Programm wird der Wert nicht-initialisierter Objekte (»x« und »y«) ausgegeben. Dies führt zu undefiniertem Verhalten.

main.cpp

#include <initializer_list>
#include <iostream>
#include <ostream>

struct pair
{ double x;
double y;

void print()
{ ::std::cout << this->x << '\n';
::std::cout << this->y << '\n'; }};

int main()
{ pair p;
p.print(); }

Protokoll
0
0
Zitat *
“When storage for an object with automatic or dynamic storage duration is obtained, the object has an indeterminate value, and if no initialization is performed for the object, that object retains an indeterminate value until that value is replaced. If an indeterminate value is produced by an evaluation, the behavior is undefined
C++ 2016, 8.6p12; C++ 2017, 11.6p12

Initialisierung mit Hilfe einer Elementfunktion

Im nächsten Beispielprogramm wird das Objekt mit einer Elementfunktion »init()« auf einen bestimmten Anfangszustand gesetzt. Das Programm zeigt nun kein undefiniertes Verhalten mehr.

main.cpp

#include <initializer_list>
#include <iostream>
#include <ostream>

struct pair
{ double x;
double y;

void set
( double const x,
double const y )
{ this->x = x;
this->y = y; }

void init()
{ this->set( 0., 0. ); }

void print()
{ ::std::cout << this->x << '\n';
::std::cout << this->y << '\n'; }};

int main()
{ pair p;
p.init();
p.print(); }

Protokoll
0
0

Initialisierung mit Hilfe eines Konstruktors

Im vorigen Beispielprogramm mußte der Klient die Elementfunktion »init()« aufrufen, um ein Objekt zu initialisieren, also auf einen wohldefinierten Anfangszustand zu setzen. Dies verkompliziert den Klienten aber und könnte auch einmal vergessen werden.

Im folgenden Beispiel wird daher ein Konstruktor  definiert, der automatisch zu Initialisierung eines Objekts eingesetzt wird, um die gleiche Aufgabe auf elegantere Weise zu erledigen.

main.cpp

#include <initializer_list>
#include <iostream>
#include <ostream>

struct pair
{ double x;
double y;

pair()
{ ::std::cout << "Konstruktor\n";
this->set( 0., 0. ); }

void set
( double const x,
double const y )
{ this->x = x;
this->y = y; }

void print()
{ ::std::cout << this->x << '\n';
::std::cout << this->y << '\n'; }};

int main()
{ pair p;
p.print(); }

Protokoll
Konstruktor
0
0

Der Konstruktor hat keinen eigenen Namen. Seine Definition ähnelt einer Funktionsdefinition. Die Konstruktordefinition hat aber keinen Ergebnistyp, und an Stelle eines eigenen Namens wird der Name der Klasse geschrieben, zu welcher der Konstruktor gehört.

Wie eine Funktionsdefinition enthält auch eine Konstruktordefinition ein Paar runder Klammern (oben »()«) und einen Block (der mit einer geschweiften Klammer beginnt und endet).

Wie die Ausgabe »Konstruktor« erkennen läßt, wird der Konstruktor bei der Erzeugung des Objekts (in »main()«) aktiviert.

Implizit definierte Konstruktoren

Wenn vom Benutzer  ein parameterloser Konstruktor definiert wird, wird kein  parameterloser Konstruktor mehr implizit definiert. Es wird aber weiterhin ein Kopierkonstruktor  stillschweigend definiert.

main.cpp

#include <initializer_list>
#include <iostream>
#include <ostream>

struct pair
{ double x;
double y;

pair()
{ this->set( 0., 0. ); }

void set( double const x, double const y )
{ this->x = x; this->y = y; }

void print()
{ ::std::cout << this->x << '\n' << this->y << '\n'; }};

int main()
{ pair p; p.set( 2, 3 );
pair q( p );
q.print(); }

Protokoll
2
3

Der implizit definierte Fehlkonstruktor führt dieselben Initialisierungen herbei wie ein benutzerdefinierter Fehlkonstruktor mit einem leeren Rumpf. Man kann sich den implizit definierten Fehlkonstruktor als vorstellen, wie den benutzerdefinierten Fehlkonstruktor »pair(){}« im folgenden Programm (das wieder undefiniertes Verhalten hat).

main.cpp

#include <initializer_list>
#include <iostream>
#include <ostream>

struct pair
{ double x;
double y;

pair(){}

void set( double const x, double const y )
{ this->x = x; this->y = y; }

void print()
{ ::std::cout << this->x << '\n' << this->y << '\n'; }};

int main()
{ pair p;
p.print(); }

Protokoll
0
0

Die Standardrichtilinien empfehlen die Initialisierung der Felder am Orte ihrer Deklaration.

C.45: Don't define a default constructor that only initializes data members; use in-class member initializers instead

Tabelle
.- Benutzer            Fehl-         Kopier-         Zuweisung
| deklariert: konstruktor konstruktor
|
|
|
| nichts vorgegeben vorgegeben vorgegeben
|
| Fehl- benutzer- vorgegeben vorgegeben
| konstruktor deklariert
|
| Kopier- nicht benutzer- vorgegeben*
| konstruktor deklariert deklariert
|
| anderen nicht vorgegeben vorgegeben
| Konstruktor deklariert
|
| Zuweisung vorgegeben vorgegeben* benutzer-
| deklariert
'-
* = veraltend

Namen von Feldern

Auch wenn dies nicht immer verboten ist, sollte der Name eines Feldes im allgemeinen nicht  mit dem Namen der Klasse übereinstimmen.

Quellenangabe *
if class T has a user-declared constructor (12.1), every non-static data member of class T shall have a name different from T. ”, 2015, 9.2.15

In dem speziellen Fall, daß eine Klasse keinen  benutzer-deklarierten Konstruktor hat, darf der Name eines Feldes jedoch mit dem Namen der Klassen übereinstimmen, wie das folgende Beispiel zeigt, in dem wir auch gleich noch eine lokale Variable der Funktion »main« wie die Klasse benannt haben.

main.cpp

#include <iostream>
#include <ostream>
#include <string>

struct entity { ::std::string entity; };

int main()
{ entity entity; ::std::cout << '"' << entity.entity << '"' << '\n'; }

::std::cout
""

Diese Erlaubnis ist wohl nur zur Kompatibilität mit der Programmiersprache C  eingeführt worden. Es ist aber nicht  erlaubt, im obigen Programm statt »entity.entity« zu schreiben: »entity.::entity::entity«, vermutlich da dies nicht mehr zur Kompatibilität mit C  benötigt wird.

Sobald eine Klasse einen benutzerdefinierten Konstruktor hat, muß jeder nicht-statische Eintrag der Klasse einen anderen Namen als die Klasse haben.

In diesem Kurs werden gleichlautende Namen gelegentlich verwendet, um zu illustrieren, daß Namen in Abhängigkeit von ihrem Kontext interpretiert werden. So steht in dem obigen Programm in »entity entity« und in »entity.entity« das Wort »entity« jeweils für eine andere Entität (Sache).

Übungsaufgaben

/   Übungsaufgabe

Definieren Sie eine Klasse, die einen Konstruktor hat, der die Zeile »Konstruktor« ausgibt, mit einer Klassendefinition, die möglichst wenige lexikalische Einheiten umfassen soll.

Legen Sie im Hauptprogramm einen Vektor an, der Objekte der von Ihnen definierte Klasse als Komponenten haben kann. Bei der Definition soll der Vektor zunächst mit einer Größe von drei angelegt werden. Fügen Sie dann zehn Objekte der von Ihnen definierte Klasse zu dem Vektor hinzu und zählen Sie wie oft dann insgesamt der von Ihnen definierte Konstruktor aufgerufen wurde.

Ausblick

(geplante Inhalte späterer Lektionen)

main.cpp

#include <iostream>

#include <initializer_list>

using namespace std;

template< class T >
static void say( T const & s ){ ::std::cout << s << '\n'; }

struct S

{
S() { say( "A" ); }

explicit S( int ) { say( "B" ); }

explicit S( double ) { say( "C" ); }

S( initializer_list<int> ) { say( "E" ); }};

int main()

{ S{};

S(1);

//S{1.2};

S{1}; }

transcript

A

B

E

main.cpp

#include <iostream>

#include <initializer_list>

using namespace std;

template< class T >
static void say( T const & s ){ ::std::cout << s << '\n'; }

struct S

{
explicit S( double ) { say( "C" ); }

S( initializer_list<int> ) { say( "E" ); }};

int main()

{ S{1.2}; }

transcript
main.cpp: In function 'int main()':
main.cpp:27:8: error: narrowing conversion of '1.2e+0' from 'double' to 'int' inside { } [-Wnarrowing]
{ S{1.2}; }
^

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 stefanram723724 stefan_ram:723724 Explizite Konstruktordefinitionen in C++ Stefan Ram, Berlin, and, or, near, uni, online, slrprd, slrprdqxx, slrprddoc, slrprd723724, slrprddef723724, 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/explizite_konstruktoren_c++