Was ist die Auswahlregel für den Kopier-/Verschiebungskonstruktor in C++? Wann kommt es zum Fallback zum Verschieben auf Kopie?
Lesezeit: 4 Minuten
Saft
Das erste Beispiel:
#include <iostream>
#include <memory>
using namespace std;
struct A {
unique_ptr<int> ref;
A(const A&) = delete;
A(A&&) = default;
A(const int i) : ref(new int(i)) { }
~A() = default;
};
int main()
{
A a[2] = { 0, 1 };
return 0;
}
Es funktioniert perfekt. Hier wird also der MOVE-Konstruktor verwendet.
Entfernen wir den Verschiebekonstruktor und fügen Sie einen Kopierkonstruktor hinzu:
#include <iostream>
#include <memory>
using namespace std;
struct A {
unique_ptr<int> ref;
A(const A&a)
: ref( a.ref.get() ? new int(*a.ref) : nullptr )
{ }
A(A&&) = delete;
A(const int i) : ref(new int(i)) { }
~A() = default;
};
int main()
{
A a[2] = { 0, 1 };
return 0;
}
Jetzt fällt die Kompilierung mit dem Fehler “Verwendung der gelöschten Funktion ‘A::A(A&&)’“
Der MOVE-Konstruktor ist also ERFORDERLICH und es gibt keinen Rückfall auf einen COPY-Konstruktor.
Jetzt entfernen wir sowohl den Kopier- als auch den Verschiebe-Konstruktor:
#include <iostream>
#include <memory>
using namespace std;
struct A {
unique_ptr<int> ref;
A(const int i) : ref(new int(i)) { }
~A() = default;
};
int main()
{
A a[2] = { 0, 1 };
return 0;
}
Und es fällt mit “Verwendung der gelöschten Funktion ‘A::A(const A&)’” Kompilierungsfehler. Jetzt ERFORDERT es einen COPY-Konstruktor!
Es gab also einen Fallback (?) vom Verschiebekonstruktor zum Kopierkonstruktor.
Wieso den? Hat jemand eine Ahnung, wie es dem C++-Standard entspricht und was eigentlich die Regel ist, zwischen den Kopier-/Verschiebungskonstruktoren auszuwählen?
Habe ich genau diese Frage nicht schon vor einer halben Stunde beantwortet >.>
– MM
11. Juli ’14 um 9:27
Diese Frage ist viel spezifischer. Es ist nicht dieselbe Frage.
– Saft
11. Juli ’14 um 9:44
MM
Es gibt keinen “Rückfall”. Es wird genannt Überlastauflösung. Wenn es mehr als einen möglichen Kandidaten für die Überladungsauflösung gibt, wird die beste Übereinstimmung nach einem komplizierten Regelwerk ausgewählt, das Sie durch das Lesen des C++-Standards oder eines Entwurfs davon finden können.
„1“ und „2“ auskommentieren: Kompilieren fehlgeschlagen
Bei der Überlastungsauflösung wird eine genaue Übereinstimmung einer Konvertierung vorgezogen.
In Ihren drei Beispielen in diesem Thread haben wir:
1: Zwei Kandidatenfunktionen; rvalue bevorzugt rvalue (wie in meinem ersten Beispiel)
A(const A&);
A(A&&); // chosen
2: Zwei Kandidatenfunktionen; rvalue bevorzugt rvalue (wie in meinem ersten Beispiel)
A(const A&);
A(A&&); // chosen
3: Eine Kandidatenfunktion; kein Wettbewerb
A(const A&); // implicitly declared, chosen
Wie bereits erwähnt, gibt es im Fall 3 keine implizite Deklaration von A(A&&), da Sie einen Destruktor haben.
Für die Überladungsauflösung spielt es keine Rolle, ob der Funktionsrumpf existiert oder nicht, sondern ob die Funktion deklariert ist (entweder explizit oder implizit).
Joseph Mansfield
Die zu verwendende Funktion wird ausgewählt, bevor geprüft wird, ob dies der Fall ist deleted oder nicht. Für den Fall, dass der Kopierkonstruktor verfügbar war und der Verschiebekonstruktor war deleted, der Move-Konstruktor war immer noch die beste Wahl von beiden. Dann sieht es, dass es ist deleted und gibt Ihnen einen Fehler.
Wenn Sie das gleiche Beispiel hätten, aber den Move-Konstruktor tatsächlich entfernt haben, anstatt ihn zu erstellen deleted, Sie werden sehen, dass es sich gut kompilieren lässt und auf den Kopierkonstruktor zurückgreift:
#include <iostream>
#include <memory>
using namespace std;
struct A {
unique_ptr<int> ref;
A(const A&a)
: ref( a.ref.get() ? new int(*a.ref) : nullptr )
{ }
A(const int i) : ref(new int(i)) { }
~A() = default;
};
int main()
{
A a[2] = { 0, 1 };
return 0;
}
Für diese Klasse ist überhaupt kein Bewegungskonstruktor deklariert (auch nicht implizit), daher kann sie nicht ausgewählt werden.
Die Wahl (der Auswahlprozess) hängt also davon ab, ob es einen Konstruktor oder zwei gibt? Das scheint verkabelt zu sein. Ein gelöschter Konstruktor und ein fehlender Konstruktor sind also nicht dasselbe?
– Saft
11. Juli ’14 um 9:16
@ user3544995 Der Auswahlprozess hängt davon ab, welche Konstruktoren deklariert sind. Es wählt den Konstruktor mit der besten Übereinstimmung, wie in allen Überladungssituationen. Und ja, a deleted-Konstruktor und fehlender Konstruktor sind nicht unbedingt das gleiche. Es ist möglich, dass ein fehlender Konstruktor implizit als . deklariert wird deleted allerdings. In diesem Fall ist dies nicht der Fall – es wird nur implizit überhaupt nicht deklariert.
– Joseph Mansfield
11. Juli ’14 um 9:17
.
2289400cookie-checkWas ist die Auswahlregel für den Kopier-/Verschiebungskonstruktor in C++? Wann kommt es zum Fallback zum Verschieben auf Kopie?yes
Habe ich genau diese Frage nicht schon vor einer halben Stunde beantwortet >.>
– MM
11. Juli ’14 um 9:27
Diese Frage ist viel spezifischer. Es ist nicht dieselbe Frage.
– Saft
11. Juli ’14 um 9:44