C++ Aufruf des Default-Konstruktors

S

Spacy

Gast
Code:
class Foo {

};

int main(int argc, char *argv[]) {
    Foo bar;
    Foo wrong(); // C4930
    return 0;
}


(MSVC++2010)
warning C4930: 'Foo wrong(void)': Funktion mit Prototyp wurde nicht aufgerufen (war eine Variablendefinition gemeint?)

mingw:
Keine Warnung



Was ist der Unterschied zwischen dem Default-Konstruktor-Aufruf mit und ohne leeren Klammern?
 
Hi,

mit Klammern ist es kein Konstruktor-Aufruf sondern eine Funktionsdeklaration.

Vergleiche mal:
Code:
int foo();
und
Code:
Foo wrong();
Bei obigem sollte es offensichtlich sein, dass es sich um eine Funktionsdeklaration handelt. :)

Das untere deklariert eine Funktion, die ein Foo zurück gibt und wrong heißt. Klingt komisch, ist aber so. :)

Nur ohne Klammern ist es ein Default-Konstruktor-Aufruf.

Code:
A w; // Default-Konstruktor
A x(); // Funktionsdeklaration
A y( w); // Copy-Konstruktor (direkte Initialisierung)
A z = w; // Copy-Konstruktor (Copy-Initialisierung)
 
Ah, vielen dank. Konnte in meinem C++ Buch keine ordentliche Unterscheidung finden.

Kann es sein, dass es in JAVA etwas anders ist?
 
Ja bei Java ist das anders, da du fuer Referenztypen immer Objekte mit new erzeugen musst.

Code:
Foo wrong;
worng = new Foo();

wrong ist am Anfang als nur eine Referenz. Da wird kein Konstruktor aufgerufen.
 
Soweit ich das bei VC++ verstanden habe, wird beim ersten ohne Klammern kein Konstruktor aufgerufen. Das heißt, der Speicher ist nicht definiert, wohingegen beim zweiten mit Klammern der Konstruktor aufgerufen wird (default für 0-Initialisierung oder der selbstdefinierte).

Daher macht es schon einen Unterschied. Die Warnung tritt wahrscheinlich wegen der leeren Klasse auf. Wenn du eine Variable einfügst, dann ist wie Warnung wahrscheinlich weg...
 
Blitzmerker schrieb:
Soweit ich das bei VC++ verstanden habe, wird beim ersten ohne Klammern kein Konstruktor aufgerufen. Das heißt, der Speicher ist nicht definiert, wohingegen beim zweiten mit Klammern der Konstruktor aufgerufen wird (default für 0-Initialisierung oder der selbstdefinierte).

Daher macht es schon einen Unterschied. Die Warnung tritt wahrscheinlich wegen der leeren Klasse auf. Wenn du eine Variable einfügst, dann ist wie Warnung wahrscheinlich weg...


Nein, total falsch.
Selbst mit einer Variable in der Klasse tritt die Warnung auf.
Beim Ersten wird auf jeden Fall der Default-Konstruktor aufgerufen, habe ich eben nochmal getestet.
Beim Zweiten wird anscheinend nicht mal ein Objekt erzeugt, weil ich beim Versuch, die Variable zu verwenden folgende Fehlermeldung erhalte:
error C2228: Links von ".n" muss sich eine Klasse/Struktur/Union befinden.


neues Beispiel
Code:
#include <iostream>
using namespace std;

class Foo {
public:
    int n;
    Foo() { n = 123; }
};

int main(int argc, char *argv[]) {
    Foo bar; // 1.
    Foo wrong(); // 2.

    cout << bar.n << endl; // ok, Ausgabe: 123
    cout << wrong.n << endl; // error C2228
    return 0;
}
 
Hast recht, das Problem war bei new
Code:
 [SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]
class[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4] Foo
{
[/SIZE][SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]int[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4] a,b,c,de,f;
};
[/SIZE][SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]int[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4] _tmain([/SIZE][SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]int[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4] argc, _TCHAR* argv[])
{
Foo*a=[/SIZE][SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]new[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4] Foo;
Foo*b=[/SIZE][SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]new[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4] Foo();
[/SIZE][SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]return[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4] 0;
}
[/SIZE]
Da hat a in der Debug Build 0xcdcdcd als Member und b 0x0.
 
Blitzmerker schrieb:
Soweit ich das bei VC++ verstanden habe, wird beim ersten ohne Klammern kein Konstruktor aufgerufen.
Nein, der Konstruktor wird immer aufgerufen, auch wenn er leer ist. Für jedes Member wird die Default-Initialisierung verwendet, wenn nicht in der Initialisierungsliste anders angegeben. Bei User-Defined-Types ist das der Konstruktor, bei den fundamentalen Typen passiert in den meisten Fällen nichts. Ausnahmen hiervon sind z.B. Array-Initialisierer oder POD-Struct-Initialisierer.
Code:
int a[10]; // unitialisiert
int b[10] = {}; // alle Elemente mit 0 initialisiert

struct POD {
  int a;
};
POD value = POD(); // value.a ist mit 0 initialisiert
POD other; // value.a ist [b]nicht[/b] initialisiert
Man darf auch
Code:
int a = int(); // wird mit 0 initialisiert
schreiben. Insbesondere um Templates schreiben zu können.


Das Artefakt, was du da im Debug-Mode siehst, ist übrigens eine spezielle Debug-Initialisierung. (das 0xcdcdcdcd) Wenn man das als Pointer verwendet und dereferenziert, gibt es einen kontrollierten Absturz.
 
Zuletzt bearbeitet:
Das ist sehr interessant, was du ansprichst...
Wenn ich ohne Konstruktor einmal mit "()" schreib und einmal ohne, dann ist das mit mit 0 initialisiert (memset), ohne wird nichts initialisiert (0xcdcdcdcd, das ist eine Füllung des Heaps vor Programmstart).

Sobal ich einen Konstruktor definiere, wird er in beiden Fällen aufgerufen...

Wenn ich eine Klasse (bspw. vector<>) in Foo einbau, dann wird der Konstruktor immer aufgerufen, dieser initialisiert aber nicht mehr die Variablen auf "0", sondern nur der Vektor wird initialisiert...

Ich finde die Umsetzung etwas inkonsistent. Was wäre eigentlich standardkonformes Verhalten?

(bin grad richtig verwirrt)
 
Code:
[SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]#include[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4][COLOR=#a31515][SIZE=4][COLOR=#a31515]"stdafx.h"[/COLOR][/SIZE]
[/COLOR][/SIZE][SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]#include[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4][COLOR=#a31515][SIZE=4][COLOR=#a31515]<vector>[/COLOR][/SIZE]
[/COLOR][/SIZE][SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]class[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4] Foo[/SIZE]
[SIZE=4]{[/SIZE]
[SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]int[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4] a,b;[/SIZE]
[SIZE=4]std::vector<[/SIZE][SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]int[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4]>c;[/SIZE]
[SIZE=4]};[/SIZE]
[SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]class[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4] Bar[/SIZE]
[SIZE=4]{[/SIZE]
[SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]int[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4] a,b;[/SIZE]
[SIZE=4]};[/SIZE]
[SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]int[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4] _tmain([/SIZE][SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]int[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4] argc, _TCHAR* argv[])[/SIZE]
[SIZE=4]{[/SIZE]
[SIZE=4]Foo*a=[/SIZE][SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]new[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4] Foo;[/SIZE]
[SIZE=4]Foo*b=[/SIZE][SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]new[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4] Foo();[/SIZE]
[SIZE=4]Bar *c=[/SIZE][SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]new[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4] Bar;[/SIZE]
[SIZE=4]Bar *d=[/SIZE][SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]new[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4] Bar();[/SIZE]
[SIZE=4][COLOR=#0000ff][SIZE=4][COLOR=#0000ff]return[/COLOR][/SIZE][/COLOR][/SIZE][SIZE=4] 0;[/SIZE]
[SIZE=4]}[/SIZE]
Die Ausgabe ist:
Konstruktoren.png
 
Hier stand Blödsinn... ich muss das mal genau im Standard nachwühlen...
 
Zuletzt bearbeitet:
Blitzmerker schrieb:
Das ist sehr interessant, was du ansprichst...
Wenn ich ohne Konstruktor einmal mit "()" schreib und einmal ohne, dann ist das mit mit 0 initialisiert (memset), ohne wird nichts initialisiert (0xcdcdcdcd, das ist eine Füllung des Heaps vor Programmstart).

Sobal ich einen Konstruktor definiere, wird er in beiden Fällen aufgerufen...

Wenn ich eine Klasse (bspw. vector<>) in Foo einbau, dann wird der Konstruktor immer aufgerufen, dieser initialisiert aber nicht mehr die Variablen auf "0", sondern nur der Vektor wird initialisiert...

Ich finde die Umsetzung etwas inkonsistent. Was wäre eigentlich standardkonformes Verhalten?

(bin grad richtig verwirrt)


Ich habe mal ein paar Auszüge aus dem Standard hier gesammelt.

(12.6.1)
When no initializer is specified for an object of (possibly cv-qualified) class type (or array thereof), or the
initializer has the form (), the object is initialized as specified in 8.5. The object is default-initialized if
there is no initializer, or value-initialized if the initializer is ().

(8.5.7)
An object whose initializer is an empty set of parentheses, i.e., (), shall be value-initialized.

(8.5.5)
To zero-initialize an object of type T means:
— if T is a scalar type (3.9), the object is set to the value of 0 (zero) converted to T;
— if T is a non-union class type, each nonstatic data member and each base-class subobject is zeroinitialized;
— if T is a union type, the object’s first named data member89) is zero-initialized;
— if T is an array type, each element is zero-initialized;
— if T is a reference type, no initialization is performed.

To default-initialize an object of type T means:
— if T is a non-POD class type (clause 9), the default constructor for T is called (and the initialization is
ill-formed if T has no accessible default constructor);
— if T is an array type, each element is default-initialized;
— otherwise, the object is zero-initialized.

To value-initialize an object of type T means:
— if T is a class type (clause 9) with a user-declared constructor (12.1), then the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
— if T is a non-union class type without a user-declared constructor, then every non-static data member
and base-class component of T is value-initialized;
— if T is an array type, then each element is value-initialized;
— otherwise, the object is zero-initialized

Nach 40-minütigem Studium würde ich sagen, die Umsetzung von MSVC++ 2010 ist standardkonform. Aber ein bißchen klarer hätten sich die Damen und Herren vom Standardkomitee in einigen Punkten ruhig schon ausdrücken können. :lol:
 
Die haben sich klar ausgedrückt.

Was hier noch anzumerken ist: Es sollte grundsätzlich ein Construktor für jede Klasse angegeben werden, ebenso wie ein Copy Constructor. Wenn das nicht passiert können sehr unerwartete Dinge passieren! Es ist unsauber keinen Constructor anzugeben!

Übrigens sollte man Objekte immer mit Initializer erstellen:
If no initializer is specified for an object, the object is default-initialized; if no initialization is performed, an
object with automatic or dynamic storage duration has indeterminate value.

"indeterminate value" sollte man grundsätzlich vermeiden!
 
IceMatrix schrieb:
Die haben sich klar ausgedrückt.

Was hier noch anzumerken ist: Es sollte grundsätzlich ein Construktor für jede Klasse angegeben werden, ebenso wie ein Copy Constructor. Wenn das nicht passiert können sehr unerwartete Dinge passieren! Es ist unsauber keinen Constructor anzugeben!

Übrigens sollte man Objekte immer mit Initializer erstellen:


"indeterminate value" sollte man grundsätzlich vermeiden!

Prinzipiell stimme ich dem zu. Ein benutzerdefinierter Kopierkonstruktor ist aber unnötig, wenn sich deine Klasse nur aus Komponenten zusammensetzt, die von sich aus schon das passende Kopierverhalten mitbringen. Beispiel:

Code:
class Bla
{
public:
    explicit Bla( const std::string& name );

private:
     std::vector< int > m_ints;
     const std::string m_name;
};

Wozu sollte man in so einem Fall die Arbeit auf sich nehmen, den Kopierkonstruktor selbst zu implementieren, wenn doch der vom Compiler selbst erstellte vollkommen ausreichend ist?

Aber wie gesagt, im Grunde stimme ich dir zu, und ich würde die Sache sogar noch ein Level weiter treiben. Viele Programmierer achten leider nicht auf Kopierbarkeit ihrer Klassen, was bei unbeabsichtigten Kopien zu subtilen Bugs im Programm führen kann. Ich würde deshalb vorschlagen, JEDE neu erstellte Klasse von Haus nichtkopierbar zu machen und diese Restriktion nur dann zu lockern, wenn es einen guten Grund gibt, weshalb sie doch kopierbar sein sollten.

Ein Klasse kann man nichtkopierbar machen, indem man Kopierkonstruktor und Kopierzuweisungsoperator als private deklariert und NICHT IMPLEMENTIERT. Versucht man dann, eine Instanz zu kopieren, meckert der Compiler oder spätestens der Linker.

Code:
class Bla
{
private:
    // bei als private deklariert und nicht implementiert
    Bla( const Bla& );
    Bla& operator = ( const Bla& );
};

// oder besser:
#include <boost/noncopyable.hpp>
class Bla : private boost::noncopyable
{
};
 
Zurück
Oben