2015-12-08 8 views
11

Ich habe meinen Code wie folgt vereinfacht.Warum C++ - Destruktor das Verhalten der Rückgabewertoptimierung beeinflussen

#include <vector> 
class NoncopyableItem { 
public: 
    NoncopyableItem() { } 
    NoncopyableItem(NoncopyableItem &&nt) { }; 
}; 
class Iterator { 
    friend class Factory; 
public: 
    ~Iterator() { } // weird 
private: 
    Iterator() { } 
    std::vector<NoncopyableItem> buffer_; 
}; 
class Factory { 
public: 
    Iterator NewIterator() { 
    return Iterator(); 
    } 
}; 
int main() { 
    Factory fa; 
    auto it = fa.NewIterator(); 
    return 0; 
} 

Ich möchte NewIterator die RVO (Rückgabewert Optimierung) in Funktion nutzen, aber ich habe den folgenden Fehler:

In file included from /usr/lib/gcc/x86_64-pc-cygwin/4.9.3/include/c++/vector:62:0, 
       from /cygdrive/c/Users/DELL/ClionProjects/destructor_test/main.cpp:1: 
/usr/lib/gcc/x86_64-pc-cygwin/4.9.3/include/c++/bits/stl_construct.h: In instantiation of 'void std::_Construct(_T1*, _Args&& ...) [with _T1 = NoncopyableItem; _Args = {const NoncopyableItem&}]': 
/usr/lib/gcc/x86_64-pc-cygwin/4.9.3/include/c++/bits/stl_uninitialized.h:75:53: required from 'static _ForwardIterator std::__uninitialized_copy<_TrivialValueTypes>::__uninit_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<const NoncopyableItem*, std::vector<NoncopyableItem> >; _ForwardIterator = NoncopyableItem*; bool _TrivialValueTypes = false]' 
/usr/lib/gcc/x86_64-pc-cygwin/4.9.3/include/c++/bits/stl_uninitialized.h:126:41: required from '_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<const NoncopyableItem*, std::vector<NoncopyableItem> >; _ForwardIterator = NoncopyableItem*]' 
/usr/lib/gcc/x86_64-pc-cygwin/4.9.3/include/c++/bits/stl_uninitialized.h:279:63: required from '_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = __gnu_cxx::__normal_iterator<const NoncopyableItem*, std::vector<NoncopyableItem> >; _ForwardIterator = NoncopyableItem*; _Tp = NoncopyableItem]' 
/usr/lib/gcc/x86_64-pc-cygwin/4.9.3/include/c++/bits/stl_vector.h:324:32: required from 'std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = NoncopyableItem; _Alloc = std::allocator<NoncopyableItem>]' 
/cygdrive/c/Users/DELL/ClionProjects/destructor_test/main.cpp:7:7: required from here 
/usr/lib/gcc/x86_64-pc-cygwin/4.9.3/include/c++/bits/stl_construct.h:75:7: error: use of deleted function 'constexpr NoncopyableItem::NoncopyableItem(const NoncopyableItem&)' 
    { ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); } 
    ^
/cygdrive/c/Users/DELL/ClionProjects/destructor_test/main.cpp:2:7: note: 'constexpr NoncopyableItem::NoncopyableItem(const NoncopyableItem&)' is implicitly declared as deleted because 'NoncopyableItem' declares a move constructor or move assignment operator 
class NoncopyableItem { 
    ^
CMakeFiles/destructor_test.dir/build.make:62: recipe for target 'CMakeFiles/destructor_test.dir/main.cpp.o' failed 

Nach cppreference.com, NewIterator() sollte das Erfordernis der RVO erfüllen. Es scheint jedoch, dass der Compiler versucht, den Standardkopiekonstruktor Iterator aufzurufen, und schlägt dann fehl, weil Iterator.buffer_ nicht kopierbar ist.

Nun zu meiner Überraschung, wenn ich den Destruktor von Iterator in L # 13 lösche, funktioniert der Code gut.

Warum beeinflusst der Destruktor das RVO-Verhalten des Compilers?

Antwort

11

Zunächst einmal vergessen Sie in diesem Zusammenhang RVO. Es ist eine legale Optimierung, aber auch wenn es passiert, muss der Code ohne es legal sein.

Also mit diesem Hintergrund betrachten wir

auto it = fa.NewIterator(); 

Diese Linie ein neues Iterator von einer temporären Iterator zu konstruieren versucht. Damit dies funktioniert, müssen wir eine der folgenden zwei :

Iterator(const Iterator&); //or 
Iterator(Iterator&&); 

Jetzt im Code geschrieben Sie, die implizit deklariert Iterator(const Iterator&); zu verwenden versuchen, in den Compiler-Fehler führen Sie, weil der Copykonstruktor zeigte der Das nicht statische Element buffer_ kann nicht kompiliert werden.

Der zweite Kandidat wird nicht generiert, da Iterator einen benutzerdefinierten Destruktor hat.

Wenn Sie den benutzerdefinierten Destruktor entfernen, generiert der Compiler den Verschiebungskonstruktor Iterator(Iterator&&); und verwendet ihn, während wir aus einem temporären Konstruktor konstruieren. Oder vielleicht wird es nicht und RVO statt, aber es könnte verwenden Sie es, und das ist der wichtige Teil.


oder ein anderer Benutzer erklärt Konstruktor, der die Linie Rechts natürlich macht. Aber oben sind die beiden Compiler-generierten, die Sie anscheinend fragen.

+1

Nicht ganz richtig. 'Iterator (const Iterator &);' wird implizit deklariert und als Standard definiert (nicht gelöscht). Der Kopierkonstruktor von 'std :: vector ' kann jedoch nicht instanziiert werden. ('std :: vector ' hat einen barrierefreien Copy-Konstruktor, was die Überprüfung der Kopierbarkeit der Elemente betrifft.) Daher der Fehler des OPs. –

+0

@ T.C. Danke, behoben. Es wird jedoch als gelöscht am 12.7.11 in N4140 definiert, oder? –

+0

Nein, der Kopierkonstruktor von 'Iterator' wird nicht wirklich gelöscht, da der Vektor nominal * * einen barrierefreien und nicht gelöschten Kopierkonstruktor hat. 'static_assert (std :: is_copy_constructible {}," ");' wird * nicht * ausgelöst. Die implizite Definition des Kopierkonstruktors von 'Iterator' löst die Instanziierung des Kopierkonstruktors des Vektors aus, was einen schweren Fehler verursacht. –