wake-up-neo.com

Inwiefern unterscheidet sich "= default" von "{}" für den Standardkonstruktor und -destruktor?

Ich habe dies ursprünglich nur als Frage zu Destruktoren gepostet, aber jetzt berücksichtige ich den Standardkonstruktor. Hier ist die ursprüngliche Frage:

Wenn ich meiner Klasse einen Destruktor geben möchte, der virtuell ist, aber ansonsten mit dem identisch ist, den der Compiler generieren würde, kann ich =default:

class Widget {
public:
   virtual ~Widget() = default;
};

Aber es scheint, dass ich mit einer leeren Definition den gleichen Effekt mit weniger Eingaben erzielen kann:

class Widget {
public:
   virtual ~Widget() {}
};

Gibt es eine Möglichkeit, wie sich diese beiden Definitionen unterschiedlich verhalten?

Basierend auf den Antworten, die für diese Frage gesendet wurden, scheint die Situation für den Standardkonstruktor ähnlich zu sein. Angesichts der Tatsache, dass es fast keinen Unterschied in der Bedeutung zwischen "=default" und "{} "Gibt es für Destruktoren fast keinen Unterschied in der Bedeutung dieser Optionen für Standardkonstruktoren? Angenommen, ich möchte einen Typ erstellen, in dem die Objekte dieses Typs sowohl erstellt als auch zerstört werden. Warum sollte ich sagen?

Widget() = default;

anstatt

Widget() {}

?

Ich entschuldige mich, wenn das Erweitern dieser Frage nach dem ursprünglichen Posten gegen einige SO Regeln verstößt. Das Posten einer fast identischen Frage für Standardkonstruktoren erschien mir als die weniger wünschenswerte Option.

146

Dies ist eine ganz andere Frage, wenn man nach Konstruktoren fragt als nach Destruktoren.

Wenn dein Destruktor virtual ist, dann ist der Unterschied vernachlässigbar wie Howard betont hat . Wenn Ihr Destruktor jedoch nicht virtuell war , ist das eine ganz andere Geschichte. Gleiches gilt für Konstrukteure.

Mit = default Syntax für spezielle Elementfunktionen (Standardkonstruktor, Konstruktoren zum Kopieren/Verschieben/Zuweisen, Destruktoren usw.) bedeutet etwas ganz anderes als einfach {}. Mit letzterem wird die Funktion "vom Benutzer bereitgestellt". Und das ändert alles.

Dies ist eine triviale Klasse gemäß der Definition von C++ 11:

struct Trivial
{
  int foo;
};

Wenn Sie versuchen, einen Standardkonstruktor zu erstellen, generiert der Compiler automatisch einen Standardkonstruktor. Gleiches gilt für das Kopieren/Verschieben und Zerstören. Da der Benutzer keine dieser Memberfunktionen bereitgestellt hat, wird diese Klasse in der C++ 11-Spezifikation als "trivial" eingestuft. Es ist daher legal, dies zu tun, wie ihren Inhalt zu memcpieren, um sie zu initialisieren und so weiter.

Dies:

struct NotTrivial
{
  int foo;

  NotTrivial() {}
};

Wie der Name schon sagt, ist dies nicht mehr trivial. Es hat einen Standardkonstruktor, der vom Benutzer bereitgestellt wird. Es ist egal, ob es leer ist; Für die Regeln von C++ 11 kann dies kein trivialer Typ sein.

Dies:

struct Trivial2
{
  int foo;

  Trivial2() = default;
};

Auch dies ist, wie der Name schon sagt, ein trivialer Typ. Warum? Weil Sie den Compiler angewiesen haben, den Standardkonstruktor automatisch zu generieren. Der Konstruktor ist daher nicht "benutzerbezogen". Daher zählt der Typ als trivial, da er keinen vom Benutzer bereitgestellten Standardkonstruktor hat.

Das = default Die Syntax ist hauptsächlich für Kopierkonstruktoren/Zuweisungen vorgesehen, wenn Sie Memberfunktionen hinzufügen, die die Erstellung solcher Funktionen verhindern. Es löst jedoch auch ein spezielles Verhalten des Compilers aus, sodass es auch in Standardkonstruktoren/-destruktoren nützlich ist.

87
Nicol Bolas

Sie sind beide nicht trivial.

Sie haben beide die gleiche No-Except-Spezifikation, abhängig von der No-Except-Spezifikation der Basen und Elemente.

Der einzige Unterschied, den ich bisher feststelle, besteht darin, dass wenn Widget eine Basis oder ein Element mit einem unzugänglichen oder gelöschten Destruktor enthält:

struct A
{
private:
    ~A();
};

class Widget {
    A a_;
public:
#if 1
   virtual ~Widget() = default;
#else
   virtual ~Widget() {}
#endif
};

Dann ist die =default solution wird kompiliert, aber Widget wird kein zerstörbarer Typ sein. Das heißt Wenn Sie versuchen, ein Widget zu zerstören, wird beim Kompilieren ein Fehler angezeigt. Aber wenn nicht, haben Sie ein Arbeitsprogramm.

Wenn Sie den vom Benutzer bereitgestellten Destruktor angeben, können die Dinge nicht kompiliert werden, unabhängig davon, ob Sie ein Widget zerstören oder nicht:

test.cpp:8:7: error: field of type 'A' has private destructor
    A a_;
      ^
test.cpp:4:5: note: declared private here
    ~A();
    ^
1 error generated.
38
Howard Hinnant

Der wichtige Unterschied zwischen

class B {
    public:
    B(){}
    int i;
    int j;
};

und

class B {
    public:
    B() = default;
    int i;
    int j;
};

ist der mit B() = default; definierte Standardkonstruktor und wird als nicht benutzerdefiniert betrachtet. Dies bedeutet, dass bei Wert-Initialisierung wie in

B* pb = new B();  // use of () triggers value-initialization

eine spezielle Art der Initialisierung, die überhaupt keinen Konstruktor verwendet, findet statt. Bei integrierten Typen führt dies zu Null-Initialisierung. Im Falle von B(){} findet dies nicht statt. Der C++ Standard n3337 § 8.5/7 besagt

Ein Objekt vom Typ T mit einem Wert zu initialisieren bedeutet:

- Wenn T ein (möglicherweise cv-qualifizierter) Klassentyp ist (Klausel 9) mit einem vom Benutzer bereitgestellten Konstruktor (12.1), dann der Standardkonstruktor für T wird aufgerufen (und die Initialisierung ist fehlerhaft, wenn T keinen zugänglichen Standardkonstruktor hat);

- Wenn T ein (möglicherweise cv-qualifizierter) Nicht-Union-Klassentyp ohne einen vom Benutzer angegebenen Konstruktor ist, wird das Objekt mit Null initialisiert und Wenn der implizit deklarierte Standardkonstruktor von T nicht trivial ist, wird dieser Konstruktor aufgerufen.

- Wenn T ein Array-Typ ist, wird jedes Element mit einem Wert initialisiert. - Andernfalls wird das Objekt mit Null initialisiert.

Beispielsweise:

#include <iostream>

class A {
    public:
    A(){}
    int i;
    int j;
};

class B {
    public:
    B() = default;
    int i;
    int j;
};

int main()
{
    for( int i = 0; i < 100; ++i) {
        A* pa = new A();
        B* pb = new B();
        std::cout << pa->i << "," << pa->j << std::endl;
        std::cout << pb->i << "," << pb->j << std::endl;
        delete pa;
        delete pb;
    }
  return 0;
}

mögliches Ergebnis:

0,0
0,0
145084416,0
0,0
145084432,0
0,0
145084416,0
//...

http://ideone.com/k8mBrd

30
4pie0