2015-05-05 28 views
5

folgenden Struktur gegeben:Kopierliste Initialisierung vs direkter Liste der Initialisierung von temporärer

struct ABC 
{ 
    ABC(){cout << "ABC" << endl;} 
    ~ABC() noexcept {cout << "~ABC" << endl;} 
    ABC(ABC const&) {cout << "copy" << endl;} 
    ABC(ABC&&) noexcept {cout << "move" << endl;} 
    ABC& operator=(ABC const&){cout << "copy=" << endl;} 
    ABC& operator=(ABC&&) noexcept {cout << "move=" << endl;} 
}; 

Die Ausgabe von:

std::pair<std::string, ABC> myPair{{}, {}}; 

ist:

ABC 
copy 
~ABC 
~ABC 

Während die Ausgabe von:

std::pair<std::string, ABC> myPair{{}, ABC{}}; 

ist:

ABC 
move 
~ABC 
~ABC 

Bei dem Versuch, den Unterschied zwischen den beiden, die ich glaube, ich habe festgestellt, dass der erste Fall wird mit Kopie-list-Initialisierung, während der zweite verwendet die direkte-list-Initialisierung eines zu verstehen unbenannte temporäre (Zahlen 7 und 2, hier, hier: http://en.cppreference.com/w/cpp/language/list_initialization).

Auf der Suche nach ähnlichen Fragen habe ich Folgendes gefunden: Why does the standard differentiate between direct-list-initialization and copy-list-initialization? und dies: Does copy list initialization invoke copy ctor conceptually?.

Die Antworten in diesen Fragen diskutieren die Tatsache, dass für die Copy-List-Initialisierung die Verwendung eines expliziten Konstruktors den Code schlecht gebildet würde. Wenn ich den Standardkonstruktor von ABC explizit mache, wird mein erstes Beispiel nicht kompiliert, aber das ist (vielleicht) eine andere Sache.

Also, die Frage ist: Warum wird das temporäre kopiert im ersten Fall aber in der zweiten verschoben? Was verhindert, dass es bei der Kopierlisten-Initialisierung verschoben wird?

Als Hinweis, den folgenden Code:

std::pair<std::string, ABC> myPair = std::make_pair<string, ABC>({}, {}); 

führt auch zu einem Aufruf an die ABC-Zug-Konstruktor (und keine Kopie Konstruktoraufruf), aber verschiedene Mechanismen beteiligt sein können.

Sie aus dem Code versuchen können (gcc-4.9.2 in C++ 14-Modus) auf: https://ideone.com/Kc8xIn

Antwort

8

Im Allgemeinen verspannt-init-Listen wie {} sind keine Ausdrücke und haben nicht eine Art. Wenn Sie eine Funktion Vorlage

template<typename T> void f(T); 

und rufen f({}) haben, sind Typ für T abgeleitet und Typ Abzug wird fehlschlagen.

Auf der anderen Seite ist ABC{} ein Prvalue-Ausdruck des Typs ABC (eine "explizite Typumwandlung in der funktionalen Notation"). Bei einem Aufruf wie f(ABC{}) kann die Funktionsvorlage aus diesem Ausdruck den Typ ABC ableiten.


In C++ 14 sowie in C 11 ++ std::pair folgende Konstruktoren [Paare.Paar]; T1 und T2 sind die Namen der Template-Parameter der std::pair Klassenvorlage:

pair(const pair&) = default; 
pair(pair&&) = default; 
constexpr pair(); 
constexpr pair(const T1& x, const T2& y); 
template<class U, class V> constexpr pair(U&& x, V&& y); 
template<class U, class V> constexpr pair(const pair<U, V>& p); 
template<class U, class V> constexpr pair(pair<U, V>&& p); 
template <class... Args1, class... Args2> 
pair(piecewise_construct_t, tuple<Args1...>, tuple<Args2...>); 

Hinweis, dass es einen Konstruktor

constexpr pair(const T1& x, const T2& y); // (C) 

Aber keine

ist
constexpr pair(T1&& x, T2&& y); 

stattdessen gibt es eine perfekt Spedition

template<class U, class V> constexpr pair(U&& x, V&& y); // (P) 

Wenn Sie versuchen, ein std::pair mit zwei initializers zu initialisieren, wobei mindestens einer von ihnen ist ein verspannt-init-Liste , der Konstruktor (P) ist nicht brauchbar, da er seine Vorlagenargumente nicht ableiten kann.

(C) ist keine Konstruktor Vorlage. Seine Parametertypen T1 const& und T2 const& sind durch die Parameter der Klassenvorlage festgelegt. Ein Verweis auf einen konstanten Typ kann aus einer leeren stained-init-Liste initialisiert werden. Dies erstellt ein temporäres Objekt, das an die Referenz gebunden ist. Da der Typ, auf den verwiesen wird, const ist, kopiert der (C) -Konstruktor seine Argumente in die Datenelemente der Klasse.


Wenn Sie ein Paar über std::pair<T,U>{ T{}, U{} } initialisieren, sind die T{} und U{} prvalue-Ausdrücke. Die Konstruktorvorlage (P) kann ihre Typen ableiten und ist praktikabel. Die nach der Typableitung erzeugte Instanziierung ist eine bessere Übereinstimmung als der (C) -Konstruktor, weil (P) rvalue-reference-Parameter erzeugt und die prvalue-Argumente an sie bindet. (C) bindet andererseits die Prvalue-Argumente an Lvalue-Referenzen.


Warum dann das Live-Beispiel das zweite Argument bewegen, wenn sie über std::pair<T,U>{ {}, U{} } genannt?

libstdC++ definiert zusätzliche Konstruktoren. Im Folgenden finden Sie einen Auszug aus der Implementierung von std::pair von 78536ab78e unter Auslassung von Funktionsdefinitionen, einigen Kommentaren und SFINAE. _T1 und _T2 sind die Namen der Vorlagenparameter der Klassenvorlage std::pair.

_GLIBCXX_CONSTEXPR pair(); 

    _GLIBCXX_CONSTEXPR pair(const _T1& __a, const _T2& __b); // (C) 

    template<class _U1, class _U2> 
constexpr pair(const pair<_U1, _U2>& __p); 

    constexpr pair(const pair&) = default; 
    constexpr pair(pair&&) = default; 

    // DR 811. 
    template<class _U1> 
constexpr pair(_U1&& __x, const _T2& __y); // (X) 

    template<class _U2> 
constexpr pair(const _T1& __x, _U2&& __y); // (E) <===================== 

    template<class _U1, class _U2> 
constexpr pair(_U1&& __x, _U2&& __y);  // (P) 

    template<class _U1, class _U2> 
constexpr pair(pair<_U1, _U2>&& __p); 

    template<typename... _Args1, typename... _Args2> 
    pair(piecewise_construct_t, tuple<_Args1...>, tuple<_Args2...>); 

Beachten Sie die (E) Konstruktor Vorlage: Es wird das erste Argument und perfekt uns darauf, die zweite kopieren. Für eine Initialisierung wie std::pair<T,U>{ {}, U{} } ist es sinnvoll, da nur ein Typ aus dem zweiten Argument abgeleitet werden muss. Es ist auch eine bessere Übereinstimmung als (C) für das zweite Argument und daher insgesamt eine bessere Übereinstimmung.

Der Kommentar "DR 811" befindet sich in den Quellen libstdC++. Es bezieht sich auf LWG DR 811, die einige SFINAE hinzufügen, aber keine neuen Konstruktoren.

Die Konstruktoren (E) und (X) sind eine Erweiterung libstdC++. Ich bin mir nicht sicher, ob es konform ist.

libC++ dagegen hat diese zusätzlichen Konstruktoren nicht. Für das Beispiel std::pair<T,U>{ {}, U{} } wird es copy the second argument sein.

Live demo with both library implementations

+1

wie kann perfekt Spedition Konstruktor für 'myPair ausgewählt werden {{}, ABC {}};' Ruf, wenn es ableiten kann nicht 'std :: string' von' {} '? –

+0

@MarcAndreson Ups, mein Schlechter. Ich habe das falsch verstanden. Wenn Sie libstdC++ verwenden, könnte dies eine Erweiterung sein. Lass mich versuchen, es nachzuschlagen. – dyp

+1

Der Grund für diese Überladungen ist meist zu unterstützen Sachen wie 'std :: paar , int *> p (Std :: unique_ptr (), 0);'. Siehe [PR 40925] (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=40925#c8), –