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?
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?
Antworten:
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");
}
constexpr
Wenn 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 constexpr
die Ausnahmespezifikation manuell angeben und dem impliziten Konstruktor anpassen.
Die Verwendung = default
bringt 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.
constexpr
Konstruktors (7.1.5) erfüllen würde , ist der implizit definierte Standardkonstruktor constexpr
."
constexpr
die 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() {}
.
constexpr
(da ein Datenelement nicht initialisiert bleibt) und seine Ausnahmespezifikation alle Ausnahmen zulässt. Ich werde das klarer machen.
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 s1
gibt einen Fehler, nicht s2
. In clang und g ++.
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.
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
= default
eher Gründe für das Tun als für das Tun = default
eines Konstruktors im Vergleich zum Tun { }
.
{}
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 ).
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 memcpy
Grunde 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, =default
wenn 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 constexpr
von Standardkonstruktoren ist ein Beispiel. Es ist nur mental einfacher zu verwenden, =default
als Funktionen mit all den anderen speziellen Schlüsselwörtern zu kennzeichnen, die von impliziert werden =default
und 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.
= default
wü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 = default
ist, 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.
a=0
Beispiel ist das Verhalten trivialer Typen, die ein separates (wenn auch verwandtes) Thema sind.
= default
und immer noch gewährt a
wird =0
? irgendwie? Glaubst du, ich könnte eine neue Frage erstellen wie "Wie man einen Konstruktor hat = default
und wie man die Felder richtig initialisiert?", übrigens hatte ich das Problem in a struct
und nicht in a class
und die App läuft korrekt, auch wenn = default
ich sie nicht benutze Fügen Sie eine minimale Struktur zu dieser Frage hinzu, wenn es eine gute ist :)
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).
Aufgrund der Ablehnung std::is_pod
und seiner Alternative std::is_trivial && std::is_standard_layout
lautet 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 Y
Layout immer noch Standard ist.
default
ist kein neues Schlüsselwort, sondern lediglich eine neue Verwendung eines bereits reservierten Schlüsselworts.