2016-06-02 23 views
1

Ich implementiere eine SizeTag Methode, die einen Größenwert und behalten Sie die l-Wert-Referenz.Explizit löschen nie zu verwendenden Kopierkonstruktor geben Kompilierungsfehler

Die Dinge funktionieren gut und in diesem Code ist die Absicht, den T&& Konstruktor zu verwenden.

Wenn ich jedoch ausdrücklich Copykonstruktor löschen wird der Compiler einen Fehler geben:

#include <cstdint> 
#include <type_traits> 
#include <utility> 

template <typename T = std::uint64_t> 
class SizeTag { 
    public: 
    using size_type = std::uint64_t; 
    using Type = std::conditional_t<std::is_lvalue_reference<T>::value, const size_type&, size_type>; 
    inline const Type& get() const { return _size; } 

    SizeTag(T&& sz) : _size(std::forward<T>(sz)) { } 
    SizeTag& operator = (const SizeTag&) = delete; 

    SizeTag(const SizeTag&) = delete; // No error if this line removed 

    private: 
    Type _size; 
}; 

template <typename T> 
SizeTag<T> make_size_tag(T&& t) { 
    return std::forward<T>(t); 
} 

int main() 
{ 
    int a = 9; 
    make_size_tag(a); 
} 

Warum ist das passiert? Der Kopierkonstruktor sollte in diesem Fall niemals aufgerufen werden.

+0

VS2015 scheint ohne Fehler zu kompilieren. – AlexD

+1

Welcher Compiler und wie schlägt er fehl? – luk32

+2

@AlexD VS hat nicht die beste Erfolgsbilanz bei der Einhaltung des Standards. –

Antwort

2

Die Funktion make_size_tag kehrt SizeTag<T> von Wert.

Lassen Sie uns rekapitulieren, wie function returning by value Werke:

  • Es gibt ein temporäres Objekt in der Regel die Rückgabewert Objekt genannt.
  • Für den Fall return expression;:
    • Der Ausdruck Objekt der Rückgabewert kopier initialisiert.
    • Dies ist ein Kopie Elision Kontext.
  • Für den Fall return { zero_or_more_items };:
    • verspannten Liste copy-list-initialisiert der Rückgabewert Objekt.
  • Wenn der aufrufende Code eine Variable mit dem Funktionsaufruf initialisiert, ist das Rückgabewertobjekt der Initialisierer. (Die genaue Form der Initialisierung kann abhängig vom aufrufenden Code variieren). Für die Initialisierung eines Objekts ist dies auch ein Kopie-Elisionskontext.

In Ihrem Code, folgert make_size_tag(a)T (der Parameter von make_size_tag) zu int&, weil dies ein perfektes Forwarding Szenario.

Die Instantiierung make_size_tag für diesen T sieht aus wie nach std::forward Ausbau aus:

SizeTag<int&> make_size_tag(int& t) 
{ 
    return t; 
} 

weil static_cast<int&>(t) genauso wie t ist, da t ist bereits ein L-Wert vom Typ int.


Wie bereits erwähnt, dieser Code copy-initializes der Rückgabewert Objekt. So, jetzt der Code verhält sich ein bisschen wie:

SizeTag<int&> temp_rv = t; 

und weil t ist kein SizeTag, die Definition von kopier Initialisierung ist, dass dies ist das gleiche wie:

SizeTag<int&> temp_rv = SizeTag<int&>(t); 

, die offensichtlich eine Kopie ruft/verschiebt Operation, um temp_rv von einem temporären Typ SizeTag<int&> zu initialisieren. Obwohl diese Kopie durch Kopieren entfernt werden würde, muss der barrierefreie Copy/Move-Konstruktor existieren.


Die Lösung von Jarod42 vorgeschlagen, Klammern um die Rückkehr Ausdruck setzen funktioniert, weil die entsprechende Initialisierung jetzt copy-list-initialization ist:

SizeTag<int&> temp_list_rv { t }; 

die temp_list_rv mit dem SizeTag<int&>(int&) Konstruktor initialisiert.


NB; Ihr Code hat einen separaten Fehler: seit Type ist const uint64_t &, die Initialisierung von _size von int erstellt eine temporäre, die zerstört wird, wenn der SizeTag Konstruktor abgeschlossen ist; und so kehrt das Tag mit einer baumelnden Referenz zurück. clang warnt davor, aber g ++ nicht.

dies zu beheben: Sie müssen entweder Type ändern die gleiche sein wie T& so bindet sie direkt an a, z.B .:

using size_type = typename std::remove_reference<T>::type; 

oder _size keine Referenz sein machen. Es scheint, dass letzteres den gesamten Zweck Ihres Tags besiegen würde, so dass Sie möglicherweise Ihr Design ein wenig überdenken müssen.

Um zu vermeiden, dass diese Danger-Referenz generiert wird, ändern Sie zu size_type & im Conditional_t. Dann wird der Compiler (vorausgesetzt, Sie verwenden nicht MSVC) auf das Problem hinweisen.

2

Eine Funktion, die class oder struct zurückgibt, kopiert das zurückgegebene Objekt als Teil des Prozesses der Rückgabe des Objekts. Immerhin muss das zurückgegebene Objekt irgendwo kopiert werden.

Obwohl ein Compiler erlaubt ist zu eliden, oder die Kopie zu optimieren, wenn der Compiler sich selbst beweisen kann, dass dies sicher ist, findet die Kopie technisch noch statt.

make_size_tag() gibt ein Objekt zurück. Die Konvertierung von T zu SizeTag<T> wird implizit mit dem Konstruktor SizeTag durchgeführt, dann wird das konstruierte Objekt bei der Rückgabe kopiert. Da der Kopierkonstruktor delete d ist, wird der Fehler gemeldet.

+0

In diesem Fall kann die Kopie nicht gelöscht werden, da NRVO nicht eingibt, außer der Rückgabeausdruck ist wörtlich 'return a;'. –

+0

@sleeptightpupper: Es ist nicht NRVO, aber RVO in diesem Fall ('t' ist nicht der Rückgabetyp). – Jarod42

+0

* "Die Kopie findet immer noch technisch statt." *. Es ist ein Nitpick, aber ich würde sagen, dass es * semantisch * stattfindet und technisch (wie in der Praxis) durchgeführt werden kann oder nicht. Nach alledem sollte das Programm formal analysiert werden, als ob die Kopie stattgefunden hätte. Nun, im Falle einer Kopie Elision sollte es auf beide Arten analysiert werden, aber es ist die Veränderung im beobachtbaren Verhalten, die eine große Ausnahme ist. – luk32

1

Sie haben {} zu verwenden, um das Kopieren/Verschieben Konstruktor in diesem Fall zu vermeiden:

template <typename T> 
SizeTag<T> make_size_tag(T&& t) { 
    return { std::forward<T>(t) } ; // Note the extra {} 
} 

Demo

die geschweiften Klammern, verwenden Sie die copy-list-initialization während ohne Sie ein temporäres Objekt erstellen, die Sie kopieren/Konstrukt verschieben (auch wenn RVO zutrifft).

Abschnitt 6.6.3 der Norm:

A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list.

+0

Was ändert sich die zusätzliche '{}'? – songyuanyao

+0

Es wäre schön zu erklären, wie sich die Semantik ändert. Oder warum kann man nicht einfach 'SizeTag &&' verwenden. – luk32

+0

Sorry, ich kann es immer noch nicht bekommen, wie vermeidet es copy/move ctor? Wird der Umzug hier noch benötigt? – songyuanyao