Machen die Klammern hinter dem Typnamen einen Unterschied zu new?

Lesezeit: 10 Minuten

Machen die Klammern hinter dem Typnamen einen Unterschied zu new
David Lesen

Wenn ‘Test’ eine gewöhnliche Klasse ist, gibt es einen Unterschied zwischen:

Test* test = new Test;

und

Test* test = new Test();

  • Dies ist verwandt (aber nicht identisch mit) stackoverflow.com/questions/1613341/…

    – Steve Jessop

    7. Dezember 2010 um 17:21 Uhr

  • Verwenden Sie einfach new Test(), um sicherzustellen, dass es mit Null initialisiert ist

    – Gesungen

    6. Juli 2016 um 5:51 Uhr

1647273612 354 Machen die Klammern hinter dem Typnamen einen Unterschied zu new
Michael Burr

Lassen Sie uns pedantisch werden, denn es gibt Unterschiede, die sich tatsächlich auf das Verhalten Ihres Codes auswirken können. Vieles des Folgenden stammt aus Kommentaren zu einem Artikel “Altes Neues”..

Manchmal wird der vom new-Operator zurückgegebene Speicher initialisiert und manchmal nicht, je nachdem, ob der Typ, den Sie neu erstellen, ein POD (plain old data) ist oder ob es sich um eine Klasse handelt, die POD-Mitglieder enthält und eine verwendet vom Compiler generierter Standardkonstruktor.

  • In C++1998 gibt es zwei Arten der Initialisierung: Null und Standard
  • In C++2003 wurde eine 3. Art der Initialisierung, die Wertinitialisierung, hinzugefügt.

Davon ausgehen:

struct A { int m; }; // POD
struct B { ~B(); int m; }; // non-POD, compiler generated default ctor
struct C { C() : m() {}; ~C(); int m; }; // non-POD, default-initialising m

In einem C++98-Compiler sollte Folgendes auftreten:

  • new A – unbestimmter Wert
  • new A() – Null-Initialisierung

  • new B – Standardkonstrukt (B::m ist nicht initialisiert)

  • new B() – Standardkonstrukt (B::m ist nicht initialisiert)

  • new C – Standardkonstrukt (C::m ist nullinitialisiert)

  • new C() – Standardkonstrukt (C::m ist nullinitialisiert)

In einem C++03-konformen Compiler sollten die Dinge so funktionieren:

  • new A – unbestimmter Wert
  • new A() – Wert initialisieren A, was eine Null-Initialisierung ist, da es sich um einen POD handelt.

  • new B – default-initializes (lässt B::m uninitialisiert)

  • new B() – Wert initialisiert B, das alle Felder mit Null initialisiert, da sein Standardctor vom Compiler generiert wird und nicht benutzerdefiniert.

  • new C – default-initialisiert C, das den default ctor aufruft.

  • new C() – Wert initialisiert C, das den Standardctor aufruft.

In allen Versionen von C++ gibt es also einen Unterschied zwischen new A und new A() weil A ein POD ist.

Und es gibt einen Unterschied im Verhalten zwischen C++98 und C++03 für den Fall new B().

Dies ist eine der staubigen Ecken von C++, die Sie in den Wahnsinn treiben kann. Wenn Sie ein Objekt konstruieren, wollen/brauchen Sie manchmal die Klammern, manchmal können Sie sie absolut nicht haben, und manchmal spielt es keine Rolle.

  • @j_random_hacker, new A() wird das Objekt standardmäßig in C++98 initialisieren, wie es mit new B(), new B, new C() und new Caber nicht mit new A. Das heißt, die Standardinitialisierung wird in C++98 immer durchgeführt, wenn entweder: 1) die Klasse kein POD ist und der Initialisierer fehlt, oder 2) der Initialisierer fehlt (). default-initialization initialisiert das Objekt mit Null, wenn es ein POD ist, ruft aber den Standardkonstruktor für Nicht-PODs auf.

    – Johannes Schaub – litb

    2. Januar 2011 um 13:32 Uhr


  • Kann jemand hinzufügen, was jetzt in C ++ 11 der Fall ist?

    – legends2k

    21. August 2012 um 4:39 Uhr


  • @ Jon: Mit C ++ 11 können Sie dies auch im Stapel tun. B obj{}; wird den Objektwert im Gegensatz zu initialisieren (auf 0s). B obj; die standardmäßig initialisiert wird (Garbage).

    – legends2k

    7. Mai 2013 um 14:03 Uhr

  • Sie sagen: „Manchmal kann man sie absolut nicht haben [parentheses]”. In welcher Situation können Sie sie nicht hinzufügen?

    – kec

    25. April 2015 um 17:07 Uhr

  • Also das tl;dr ist das new A gibt Mitgliedern einen unbestimmten Wert und new A() initialisiert Member-Werte auf 0 … es sei denn A hat einen Destruktor definiert, in diesem Fall geben beide Ausdrücke Mitgliedern unbestimmte Werte … es sei denn A hat auch einen Konstruktor definiert, in diesem Fall initialisieren beide Ausdrücke Mitglieder mit Nullen … es sei denn, es handelt sich in diesem Fall um einen C++ 03-Compiler new A() wird stattdessen die Mitglieder “Wert initialisieren”, was irgendwie anders ist (?). So einfach.

    – BlueRaja – Danny Pflughoeft

    6. März 2017 um 20:02 Uhr

1647273612 949 Machen die Klammern hinter dem Typnamen einen Unterschied zu new
kfson

new Thing(); ist explizit, dass Sie einen Konstruktor namens While wollen new Thing; wird so verstanden, dass es Ihnen nichts ausmacht, wenn der Konstruktor nicht aufgerufen wird.

Bei Verwendung für eine Struktur/Klasse mit einem benutzerdefinierten Konstruktor gibt es keinen Unterschied. Wenn es von einer trivialen Struktur/Klasse aufgerufen wird (z struct Thing { int i; };) dann new Thing; ist wie malloc(sizeof(Thing)); wohingegen new Thing(); ist wie calloc(sizeof(Thing)); – Es wird mit Null initialisiert.

Das Gotcha liegt dazwischen:

struct Thingy {
  ~Thingy(); // No-longer a trivial class
  virtual WaxOn();
  int i;
};

Das Verhalten von new Thingy; vs new Thingy(); in diesem Fall zwischen C++98 und C++2003 geändert. Siehe Michael Burrs Erklärung für das Wie und Warum.

Nein, sie sind gleich. Aber es gibt einen Unterschied zwischen:

Test t;      // create a Test called t

und

Test t();   // declare a function called t which returns a Test

Das liegt an der Grundregel von C++ (und C): Wenn etwas möglicherweise eine Deklaration sein kann, dann ist es eine Deklaration.

Bearbeiten: In Bezug auf die Initialisierungsprobleme in Bezug auf POD- und Nicht-POD-Daten möchte ich, obwohl ich allem Gesagten zustimme, nur darauf hinweisen, dass diese Probleme nur gelten, wenn das neue oder anderweitig konstruierte Ding keinen Benutzer hat. definierter Konstruktor. Wenn es einen solchen Konstruktor gibt, wird er verwendet. Für 99,99 % der vernünftig gestalteten Klassen wird es einen solchen Konstruktor geben, und daher können die Probleme ignoriert werden.

  • Beachten Sie, dass dies ein besonders wichtiger Punkt ist, da die Zeile “Test t(5);” entspricht “Test t = Test(5);” — aber “Test t();” unterscheidet sich sehr von “Test t = Test();”. +1

    – ojrak

    6. März 2009 um 20:03 Uhr

  • -1, ich stimme Ihrer Aussage nicht zu, dass die Probleme ignoriert werden können. Sie müssen die Regeln nicht genau kennen, aber Sie sollten sich ihrer bewusst sein, falls Sie eine Klasse ohne benutzerdefinierten Standardkonstruktor neu erstellen müssen (Sie sollten dann entweder den Konstruktor schreiben oder die Regeln nachschlagen).

    – Avakar

    6. März 2010 um 7:02 Uhr

  • -1 für eine bekannte falsche Antwort. Ihre Bearbeitung ignoriert das Vorhandensein von Code, der von ehemaligen C-Programmierern geschrieben wurde, die Konstruktoren nicht verstanden/verwendet haben.

    – Tom

    16. April 2010 um 11:00 Uhr

  • Was ist mit Klassen wie struct point { float v[3]; };? Für solche Dinge wäre ein Konstruktor eine schlechte Idee, da er all die netten Eigenschaften verhindern würde, die mit POD und Aggregat einhergehen. Also “die Probleme können ignoriert werden” ist einfach falsch, imo.

    – ich22

    2. Januar 2011 um 3:56 Uhr

  • Aber sie sind nicht gleich. Diese Antwort ist schlichtweg falsch. Es sollte behoben oder entfernt werden, da es, gemessen an der hohen Anzahl an positiven Stimmen, einige Verwirrung gestiftet hat.

    – Juanchopanza

    11. August 2014 um 5:59 Uhr

1647273613 252 Machen die Klammern hinter dem Typnamen einen Unterschied zu new
bayda

Im Allgemeinen haben wir im ersten Fall eine Standardinitialisierung und im zweiten Fall eine Wertinitialisierung.

Zum Beispiel: im Fall mit int (POD-Typ):

  • int* test = new int – Wir haben eine beliebige Initialisierung und der Wert von *test kann beliebig sein.

  • int* test = new int() – *Test hat den Wert 0.

Das nächste Verhalten hing von Ihrem Typ Test ab. Wir haben verschiedene Fälle: Test hat einen Standardkonstruktor, Test hat einen Standardkonstruktor generiert, Test enthält ein POD-Mitglied, kein POD-Mitglied …

1647273614 338 Machen die Klammern hinter dem Typnamen einen Unterschied zu new
Evan Shaw

Angenommen, Test ist eine Klasse mit einem definierten Konstruktor, gibt es keinen Unterschied. Die letztere Form macht es etwas klarer, dass der Konstruktor von Test ausgeführt wird, aber das war es auch schon.

1647273615 477 Machen die Klammern hinter dem Typnamen einen Unterschied zu new
Das ist einfach kitschig

Die Regeln für new sind analog zu dem, was passiert, wenn Sie ein Objekt mit automatischer Speicherdauer initialisieren (obwohl die Syntax aufgrund der mühsamen Analyse etwas anders sein kann).

Wenn ich sage:

int my_int; // default-initialize → indeterminate (non-class type)

Dann my_int hat einen unbestimmten Wert, da es sich um einen Nicht-Klassentyp handelt. Alternativ kann ich Wert initialisieren my_int (was für Nicht-Klassen-Typen mit Null initialisiert wird) wie folgt:

int my_int{}; // value-initialize → zero-initialize (non-class type)

(Natürlich kann ich nicht verwenden () denn das wäre eine Funktionsdeklaration, aber int() funktioniert genauso wie int{} ein Provisorium zu konstruieren.)

Während für Klassentypen:

Thing my_thing; // default-initialize → default ctor (class type)
Thing my_thing{}; // value-initialize → default-initialize → default ctor (class type)

Der Standardkonstruktor wird aufgerufen, um eine zu erstellen Thingkeine Ausnahmen.

Die Regeln sind also mehr oder weniger:

  • Ist es ein Klassentyp?
    • JAWOHL: Der Standardkonstruktor wird aufgerufen, unabhängig davon, ob er mit Werten initialisiert ist (with {}) oder default-initialisiert (ohne {}). (Es gibt ein zusätzliches vorheriges Zeroing-Verhalten bei der Wertinitialisierung, aber der Standardkonstruktor hat immer das letzte Wort.)
    • NEIN: Wurden {} benutzt?
      • JAWOHL: Das Objekt wird mit Werten initialisiert, was bei Nicht-Klassentypen mehr oder weniger nur mit Null initialisiert wird.
      • NEIN: Das Objekt wird standardmäßig initialisiert, was es bei Nicht-Klassentypen mit einem unbestimmten Wert belässt (es wird effektiv nicht initialisiert).

Diese Regeln übersetzen genau zu new Syntax, mit der hinzugefügten Regel that () kann ersetzt werden {} da new wird nie als Funktionsdeklaration geparst. Damit:

int* my_new_int = new int; // default-initialize → indeterminate (non-class type)
Thing* my_new_thing = new Thing; // default-initialize → default ctor (class type)
int* my_new_zeroed_int = new int(); // value-initialize → zero-initialize (non-class type)
     my_new_zeroed_int = new int{}; // ditto
       my_new_thing = new Thing(); // value-initialize → default-initialize → default ctor (class type)

(Diese Antwort enthält konzeptionelle Änderungen in C ++ 11, die die oberste Antwort derzeit nicht enthält. Insbesondere eine neue Skalar- oder POD-Instanz, die einen unbestimmten Wert haben würde, ist jetzt technisch standardmäßig initialisiert (was für POD-Typen technisch ruft einen trivialen Standardkonstruktor auf. Dies führt zwar nicht zu einer großen praktischen Änderung des Verhaltens, vereinfacht jedoch die Regeln etwas.)

1647273615 473 Machen die Klammern hinter dem Typnamen einen Unterschied zu new
uqb

Als Ergänzung zur Antwort von Michael Burr habe ich unten einige Beispielcodes geschrieben:

#include <iostream>

struct A1 {
    int i;
    int j;
};

struct B {
    int k;
    B() : k(4) {}
    B(int k_) : k(k_) {}
};

struct A2 {
    int i;
    int j;
    B b;
};

struct A3 {
    int i;
    int j;
    B b;
    A3() : i(1), j(2), b(5) {}
    A3(int i_, int j_, B b_): i(i_), j(j_), b(b_) {}
};

int main() {
    {
        std::cout << "Case#1: POD without ()\n";
        A1 a1 = {1, 2};
        std::cout << a1.i << " " << a1.j << std::endl;
        A1* a = new (&a1) A1;
        std::cout << a->i << " " << a->j  << std::endl;
    }
    {
        std::cout << "Case#2: POD with ()\n";
        A1 a1 = {1, 2};
        std::cout << a1.i << " " << a1.j << std::endl;
        A1* a = new (&a1) A1();
        std::cout << a->i << " " << a->j  << std::endl;
    }
    {
        std::cout << "Case#3: non-POD without ()\n";
        A2 a1 = {1, 2, {3}};
        std::cout << a1.i << " " << a1.j << " " << a1.b.k << std::endl;
        A2* a = new (&a1) A2;
        std::cout << a->i << " " << a->j << " " << a->b.k << std::endl;
    }
    {
        std::cout << "Case#4: non-POD with ()\n";
        A2 a1 = {1, 2, {3}};
        std::cout << a1.i << " " << a1.j << " " << a1.b.k  << std::endl;
        A2* a = new (&a1) A2();
        std::cout << a->i << " " << a->j << " " << a1.b.k << std::endl;
    }
    {
        std::cout << "Case#5: user-defined-ctor class without ()\n";
        A3 a1 = {11, 22, {33}};
        std::cout << a1.i << " " << a1.j << " " << a1.b.k << std::endl;
        A3* a = new (&a1) A3;
        std::cout << a->i << " " << a->j << " " << a->b.k << std::endl;
    }
    {
        std::cout << "Case#6: user-defined-ctor class with ()\n";
        A3 a1 = {11, 22, {33}};
        std::cout << a1.i << " " << a1.j << " " << a1.b.k  << std::endl;
        A3* a = new (&a1) A3();
        std::cout << a->i << " " << a->j << " " << a1.b.k << std::endl;
    }
    return 0;
}

/*
output with GCC11.1(C++20)
Case#1: POD without ()
1 2
1 2
Case#2: POD with ()
1 2
0 0
Case#3: non-POD without ()
1 2 3
1 2 4
Case#4: non-POD with ()
1 2 3
0 0 4
Case#5: user-defined-ctor class without ()
11 22 33
1 2 5
Case#6: user-defined-ctor class with ()
11 22 33
1 2 5
*/

1002260cookie-checkMachen die Klammern hinter dem Typnamen einen Unterschied zu new?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy