Die neue Syntax "= default" in C ++ 11


136

Ich verstehe nicht, warum ich das jemals tun sollte:

struct S { 
    int a; 
    S(int aa) : a(aa) {} 
    S() = default; 
};

Warum nicht einfach sagen:

S() {} // instead of S() = default;

Warum dafür eine neue Syntax einführen?


30
Nitpick: defaultist kein neues Schlüsselwort, sondern lediglich eine neue Verwendung eines bereits reservierten Schlüsselworts.


Mey be Diese Frage könnte Ihnen helfen.
FreeNickname

7
Zusätzlich zu den anderen Antworten würde ich auch argumentieren, dass '= default;' ist selbstdokumentierender.
Mark

Antworten:


136

Ein standardmäßiger Standardkonstruktor ist speziell so definiert wie ein benutzerdefinierter Standardkonstruktor ohne Initialisierungsliste und mit einer leeren zusammengesetzten Anweisung.

§12.1 / 6 [class.ctor] Ein Standardkonstruktor, der standardmäßig und nicht als gelöscht definiert ist, wird implizit definiert, wenn er zum Erstellen eines Objekts seines Klassentyps verwendet wird oder wenn er nach seiner ersten Deklaration explizit standardmäßig verwendet wird. Der implizit definierte Standardkonstruktor führt den Satz von Initialisierungen der Klasse aus, die von einem benutzerdefinierten Standardkonstruktor für diese Klasse ohne ctor-initializer (12.6.2) und eine leere zusammengesetzte Anweisung ausgeführt würden. [...]

Obwohl sich beide Konstruktoren gleich verhalten, wirkt sich die Bereitstellung einer leeren Implementierung auf einige Eigenschaften der Klasse aus. Wenn Sie einen benutzerdefinierten Konstruktor angeben, obwohl er nichts tut, ist der Typ kein Aggregat und auch nicht trivial . Wenn Sie möchten, dass Ihre Klasse ein aggregierter oder trivialer Typ ist (oder nach Transitivität ein POD-Typ), müssen Sie verwenden = default.

§8.5.1 / 1 [dcl.init.aggr] Ein Aggregat ist ein Array oder eine Klasse ohne vom Benutzer bereitgestellte Konstruktoren, [und ...]

§12.1 / 5 [class.ctor] Ein Standardkonstruktor ist trivial, wenn er nicht vom Benutzer bereitgestellt wird und [...]

§9 / 6 [Klasse] Eine triviale Klasse ist eine Klasse mit einem trivialen Standardkonstruktor und [...]

Demonstrieren:

#include <type_traits>

struct X {
    X() = default;
};

struct Y {
    Y() { };
};

int main() {
    static_assert(std::is_trivial<X>::value, "X should be trivial");
    static_assert(std::is_pod<X>::value, "X should be POD");
    
    static_assert(!std::is_trivial<Y>::value, "Y should not be trivial");
    static_assert(!std::is_pod<Y>::value, "Y should not be POD");
}

constexprWenn ein Konstruktor explizit voreingestellt wird, wird er außerdem ausgeführt, wenn der implizite Konstruktor gewesen wäre, und es wird ihm dieselbe Ausnahmespezifikation gegeben, die der implizite Konstruktor gehabt hätte. In dem von Ihnen angegebenen Fall wäre dies nicht der implizite Konstruktor gewesen constexpr(da ein Datenelement nicht initialisiert würde), und es hätte auch eine leere Ausnahmespezifikation, sodass es keinen Unterschied gibt. Aber ja, im allgemeinen Fall können Sie constexprdie Ausnahmespezifikation manuell angeben und dem impliziten Konstruktor anpassen.

Die Verwendung = defaultbringt eine gewisse Einheitlichkeit mit sich, da sie auch mit Konstruktoren und Destruktoren zum Kopieren / Verschieben verwendet werden kann. Ein leerer Kopierkonstruktor führt beispielsweise nicht dasselbe aus wie ein standardmäßiger Kopierkonstruktor (der eine mitgliedsweise Kopie seiner Mitglieder ausführt). Die einheitliche Verwendung der = default(oder = delete) Syntax für jede dieser speziellen Elementfunktionen erleichtert das Lesen Ihres Codes, indem Sie Ihre Absicht explizit angeben.


Fast. 12.1 / 6: "Wenn dieser vom Benutzer geschriebene Standardkonstruktor die Anforderungen eines constexprKonstruktors (7.1.5) erfüllen würde , ist der implizit definierte Standardkonstruktor constexpr."
Casey

Tatsächlich ist 8.4.2 / 2 informativer: "Wenn eine Funktion bei ihrer ersten Deklaration explizit als Standard festgelegt wird, wird (a) implizit davon ausgegangen, dass constexprdie implizite Deklaration gleich ist, (b) wird implizit davon ausgegangen, dass sie dieselbe hat Ausnahmespezifikation, als ob sie implizit deklariert worden wäre (15.4), ... "Es macht in diesem speziellen Fall keinen Unterschied, hat aber im Allgemeinen foo() = default;einen leichten Vorteil gegenüber foo() {}.
Casey

2
Sie sagen, es gibt keinen Unterschied und erklären dann die Unterschiede?

@hvd In diesem Fall gibt es keinen Unterschied, da die implizite Deklaration nicht wäre constexpr(da ein Datenelement nicht initialisiert bleibt) und seine Ausnahmespezifikation alle Ausnahmen zulässt. Ich werde das klarer machen.
Joseph Mansfield

2
Danke für die Klarstellung. Es scheint immer noch ein Unterschied zu sein, obwohl mit constexpr(dem erwähnten Sie sollte keinen Unterschied machen hier): struct S1 { int m; S1() {} S1(int m) : m(m) {} }; struct S2 { int m; S2() = default; S2(int m) : m(m) {} }; constexpr S1 s1 {}; constexpr S2 s2 {};Nur s1gibt einen Fehler, nicht s2. In clang und g ++.

10

Ich habe ein Beispiel, das den Unterschied zeigt:

#include <iostream>

using namespace std;
class A 
{
public:
    int x;
    A(){}
};

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


int main() 
{ 
    int x = 5;
    new(&x)A(); // Call for empty constructor, which does nothing
    cout << x << endl;
    new(&x)B; // Call for default constructor
    cout << x << endl;
    new(&x)B(); // Call for default constructor + Value initialization
    cout << x << endl;
    return 0; 
} 

Ausgabe:

5
5
0

Wie wir sehen können, initialisiert der Aufruf für den leeren A () -Konstruktor die Mitglieder nicht, während B () dies tut.


7
Würden Sie bitte diese Syntax erklären -> new (& x) A ();
Vencat

5
Wir erstellen ein neues Objekt im Speicher, das von der Adresse der Variablen x gestartet wird (anstelle der neuen Speicherzuordnung). Diese Syntax wird verwendet, um ein Objekt im vorab zugewiesenen Speicher zu erstellen. Wie in unserem Fall ist die Größe von B = die Größe von int, so dass new (& x) A () anstelle der Variablen x ein neues Objekt erstellt.
Slavenskij

Danke für Ihre Erklärung.
Vencat

1
Ich erhalte unterschiedliche Ergebnisse mit gcc 8.3: ideone.com/XouXux
Adam.Er8

Selbst mit C ++ 14 erhalte
Mayank Bhushan

9

n2210 bietet einige Gründe:

Die Verwaltung von Standardeinstellungen weist mehrere Probleme auf:

  • Konstruktordefinitionen sind gekoppelt; Durch das Deklarieren eines Konstruktors wird der Standardkonstruktor unterdrückt.
  • Die Destruktor-Standardeinstellung ist für polymorphe Klassen ungeeignet und erfordert eine explizite Definition.
  • Sobald ein Standard unterdrückt ist, gibt es keine Möglichkeit, ihn wiederzubeleben.
  • Standardimplementierungen sind häufig effizienter als manuell angegebene Implementierungen.
  • Nicht standardmäßige Implementierungen sind nicht trivial, was sich auf die Typensemantik auswirkt, z. B. macht einen Typ nicht POD.
  • Es gibt keine Möglichkeit, eine spezielle Mitgliedsfunktion oder einen globalen Operator zu verbieten, ohne einen (nicht trivialen) Ersatz zu deklarieren.

type::type() = default;
type::type() { x = 3; }

In einigen Fällen kann sich der Klassenkörper ändern, ohne dass die Definition der Elementfunktion geändert werden muss, da sich die Standardeinstellung mit der Deklaration zusätzlicher Mitglieder ändert.

Siehe Dreierregel wird mit C ++ 11 zu Fünferregel? ::

Beachten Sie, dass der Verschiebungskonstruktor und der Verschiebungszuweisungsoperator nicht für eine Klasse generiert werden, die explizit eine der anderen speziellen Elementfunktionen deklariert. Der Kopierkonstruktor und der Kopierzuweisungsoperator werden nicht für eine Klasse generiert, die explizit einen Verschiebungskonstruktor oder eine Verschiebung deklariert Zuweisungsoperator, und dass eine Klasse mit einem explizit deklarierten Destruktor und einem implizit definierten Kopierkonstruktor oder einem implizit definierten Kopierzuweisungsoperator als veraltet betrachtet wird


1
Sie sind = defaulteher Gründe für das Tun als für das Tun = defaulteines Konstruktors im Vergleich zum Tun { }.
Joseph Mansfield

@JosephMansfield Das stimmt, aber da {}war schon ein Merkmal der Sprache vor der Einführung =default, diese Gründe haben verlassen implizit auf die Unterscheidung (zB „es gibt kein Mittel wiederzubeleben [a unterdrückt default]“ impliziert , dass {}ist nicht auf den Standard gleichwertig ).
Kyle Strand

7

In einigen Fällen ist es eine Frage der Semantik. Bei Standardkonstruktoren ist dies nicht sehr offensichtlich, bei anderen vom Compiler generierten Elementfunktionen wird dies jedoch deutlich.

Für den Standardkonstruktor wäre es möglich gewesen, jeden Standardkonstruktor mit einem leeren Körper als Kandidaten für einen trivialen Konstruktor zu betrachten, genau wie bei Verwendung =default. Immerhin waren die alten leeren Standardkonstruktoren legal C ++ .

struct S { 
  int a; 
  S() {} // legal C++ 
};

Ob der Compiler diesen Konstruktor als trivial versteht oder nicht, ist in den meisten Fällen außerhalb von Optimierungen (manuell oder Compiler) irrelevant.

Dieser Versuch, leere Funktionskörper als "Standard" zu behandeln, wird jedoch für andere Arten von Elementfunktionen vollständig abgebrochen. Betrachten Sie den Kopierkonstruktor:

struct S { 
  int a; 
  S() {}
  S(const S&) {} // legal, but semantically wrong
};

Im obigen Fall ist der mit einem leeren Körper geschriebene Kopierkonstruktor jetzt falsch . Es kopiert eigentlich nichts mehr. Dies ist eine ganz andere Semantik als die Standard-Semantik des Kopierkonstruktors. Für das gewünschte Verhalten müssen Sie Code schreiben:

struct S { 
  int a; 
  S() {}
  S(const S& src) : a(src.a) {} // fixed
};

Selbst in diesem einfachen Fall wird es für den Compiler jedoch immer schwieriger, zu überprüfen, ob der Kopierkonstruktor mit dem identisch ist, den er selbst generieren würde, oder zu erkennen, dass der Kopierkonstruktor trivial ist (im memcpyGrunde genommen äquivalent zu a) ). Der Compiler müsste jeden Mitgliedsinitialisiererausdruck überprüfen und sicherstellen, dass er mit dem Ausdruck identisch ist, um auf das entsprechende Mitglied der Quelle zuzugreifen, und nichts anderes. Stellen Sie sicher, dass keine Mitglieder eine nicht triviale Standardkonstruktion usw. haben. Dies ist in gewisser Weise rückwärts Der Compiler würde verwenden, um zu überprüfen, ob seine eigenen generierten Versionen dieser Funktion trivial sind.

Betrachten Sie dann den Kopierzuweisungsoperator, der besonders im nicht trivialen Fall noch haariger werden kann. Es ist eine Tonne Kesselplatte, die Sie nicht für viele Klassen schreiben müssen, aber in C ++ 03 trotzdem gezwungen sind:

struct T { 
  std::shared_ptr<int> b; 
  T(); // the usual definitions
  T(const T&);
  T& operator=(const T& src) {
    if (this != &src) // not actually needed for this simple example
      b = src.b; // non-trivial operation
    return *this;
};

Das ist ein einfacher Fall, aber es ist bereits mehr Code, als Sie jemals gezwungen sein würden, für einen so einfachen Typ wie zu schreiben T(insbesondere, wenn wir Verschiebungsoperationen in den Mix werfen). Wir können uns nicht auf einen leeren Körper verlassen, der "Standardeinstellungen ausfüllen" bedeutet, da der leere Körper bereits vollkommen gültig ist und eine klare Bedeutung hat. Wenn der leere Körper verwendet würde, um "die Standardeinstellungen ausfüllen" zu bezeichnen, gäbe es keine Möglichkeit, explizit einen No-Op-Kopierkonstruktor oder ähnliches zu erstellen.

Es ist wieder eine Frage der Beständigkeit. Der leere Körper bedeutet "nichts tun", aber für Dinge wie Kopierkonstruktoren möchten Sie wirklich nicht "nichts tun", sondern "alles tun, was Sie normalerweise tun würden, wenn Sie nicht unterdrückt würden". Daher =default. Dies ist erforderlich, um unterdrückte, vom Compiler generierte Elementfunktionen wie Kopier- / Verschiebungskonstruktoren und Zuweisungsoperatoren zu überwinden. Es ist dann nur "offensichtlich", dass es auch für den Standardkonstruktor funktioniert.

Es wäre vielleicht schön gewesen, einen Standardkonstruktor mit leeren Körpern zu erstellen, und triviale Element- / Basiskonstruktoren würden ebenso wie trivial betrachtet, =defaultwenn nur älterer Code in einigen Fällen optimaler gemacht würde, aber der meiste Code auf niedriger Ebene basiert auf trivialem Code Standardkonstruktoren für Optimierungen basieren auch auf einfachen Kopierkonstruktoren. Wenn Sie alle Ihre alten Kopierkonstruktoren "reparieren" müssen, ist es auch nicht sehr schwierig, alle Ihre alten Standardkonstruktoren zu reparieren. Es ist auch viel klarer und offensichtlicher =default, wenn Sie eine explizite Bezeichnung für Ihre Absichten verwenden.

Es gibt noch einige andere Dinge, die vom Compiler generierte Elementfunktionen tun, die Sie explizit an der Unterstützung vornehmen müssten. Die Unterstützung constexprvon Standardkonstruktoren ist ein Beispiel. Es ist nur mental einfacher zu verwenden, =defaultals Funktionen mit all den anderen speziellen Schlüsselwörtern zu kennzeichnen, die von impliziert werden =defaultund die eines der Themen von C ++ 11 waren: die Sprache einfacher machen. Es gibt immer noch viele Warzen und rückenkompatible Kompromisse, aber es ist klar, dass es ein großer Fortschritt gegenüber C ++ 03 ist, wenn es um Benutzerfreundlichkeit geht.


Ich hatte ein Problem , wo ich erwartet = defaultwürde a=0;und war es nicht! Ich musste es zugunsten von fallen lassen : a(0). Ich bin immer noch verwirrt darüber, wie nützlich das = defaultist, geht es um Leistung? Wird es irgendwo kaputt gehen, wenn ich es einfach nicht benutze = default? Ich habe versucht, alle Antworten hier zu lesen. Ich bin neu in einigen C ++ - Dingen und habe große Probleme, sie zu verstehen.
Wassermann Power

@AquariusPower: Es geht nicht "nur" um die Leistung, sondern in einigen Fällen auch um Ausnahmen und andere Semantiken. Ein voreingestellter Operator kann nämlich trivial sein, ein nicht standardmäßiger Operator kann jedoch niemals trivial sein, und einige Codes verwenden Metaprogrammiertechniken, um das Verhalten von Typen mit nicht trivialen Operationen zu ändern oder sogar zu verbieten. Ihr a=0Beispiel ist das Verhalten trivialer Typen, die ein separates (wenn auch verwandtes) Thema sind.
Sean Middleditch

Bedeutet das, dass es möglich ist = defaultund immer noch gewährt awird =0? irgendwie? Glaubst du, ich könnte eine neue Frage erstellen wie "Wie man einen Konstruktor hat = defaultund wie man die Felder richtig initialisiert?", übrigens hatte ich das Problem in a structund nicht in a classund die App läuft korrekt, auch wenn = defaultich sie nicht benutze Fügen Sie eine minimale Struktur zu dieser Frage hinzu, wenn es eine gute ist :)
Aquarius Power

1
@AquariusPower: Sie können nicht statische Initialisierer für Datenelemente verwenden. Schreiben Sie Ihre Struktur wie folgt: struct { int a = 0; };Wenn Sie dann entscheiden, dass Sie einen Konstruktor benötigen, können Sie ihn als Standard festlegen. Beachten Sie jedoch, dass der Typ nicht trivial ist (was in Ordnung ist).
Sean Middleditch

2

Aufgrund der Ablehnung std::is_podund seiner Alternative std::is_trivial && std::is_standard_layoutlautet der Ausschnitt aus der Antwort von @JosephMansfield:

#include <type_traits>

struct X {
    X() = default;
};

struct Y {
    Y() {}
};

int main() {
    static_assert(std::is_trivial_v<X>, "X should be trivial");
    static_assert(std::is_standard_layout_v<X>, "X should be standard layout");

    static_assert(!std::is_trivial_v<Y>, "Y should not be trivial");
    static_assert(std::is_standard_layout_v<Y>, "Y should be standard layout");
}

Beachten Sie, dass das YLayout immer noch Standard ist.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.