为什么 C++ 析构函数会影响 return 值优化的行为
why C++ destuctor affect the behavior of return value optimization
我已经简化了我的代码如下。
#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;
}
我想在函数 NewIterator
中利用 RVO(return 值优化),但出现以下错误:
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
根据cppreference.com,NewIterator()
应该满足RVO的要求。但是,似乎编译器尝试调用 Iterator
的默认复制构造函数,然后失败,因为 Iterator.buffer_
是不可复制的。
好吧,令我惊讶的是,如果我在 L#13 中删除 Iterator
的析构函数,代码可以正常工作。
为什么析构函数会影响编译器的 RVO 行为?
首先,在这种情况下忘记 RVO。这是一个合法的优化,但即使它确实发生了,没有它的代码也必须是合法的。
所以考虑到这一点,我们看看
auto it = fa.NewIterator();
此行尝试从临时 Iterator
构建新的 Iterator
。为此,我们需要以下两项之一†:
Iterator(const Iterator&); //or
Iterator(Iterator&&);
现在在您发布的代码中,尝试使用隐式声明的 Iterator(const Iterator&);
将导致您显示的编译器错误,因为非静态成员 buffer_
的复制构造函数无法编译。
第二个候选项没有生成,因为Iterator
有一个用户定义的析构函数。
如果删除用户定义的析构函数,编译器将生成移动构造函数 Iterator(Iterator&&);
并在我们从临时构造函数时使用它。或者它可能不会并改为执行 RVO,但它可以 使用它,这是重要的部分。
†或者其他一些用户声明的构造函数当然使该行合法。但上面是您显然询问的两个常见的编译器生成的。
我已经简化了我的代码如下。
#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;
}
我想在函数 NewIterator
中利用 RVO(return 值优化),但出现以下错误:
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
根据cppreference.com,NewIterator()
应该满足RVO的要求。但是,似乎编译器尝试调用 Iterator
的默认复制构造函数,然后失败,因为 Iterator.buffer_
是不可复制的。
好吧,令我惊讶的是,如果我在 L#13 中删除 Iterator
的析构函数,代码可以正常工作。
为什么析构函数会影响编译器的 RVO 行为?
首先,在这种情况下忘记 RVO。这是一个合法的优化,但即使它确实发生了,没有它的代码也必须是合法的。
所以考虑到这一点,我们看看
auto it = fa.NewIterator();
此行尝试从临时 Iterator
构建新的 Iterator
。为此,我们需要以下两项之一†:
Iterator(const Iterator&); //or
Iterator(Iterator&&);
现在在您发布的代码中,尝试使用隐式声明的 Iterator(const Iterator&);
将导致您显示的编译器错误,因为非静态成员 buffer_
的复制构造函数无法编译。
第二个候选项没有生成,因为Iterator
有一个用户定义的析构函数。
如果删除用户定义的析构函数,编译器将生成移动构造函数 Iterator(Iterator&&);
并在我们从临时构造函数时使用它。或者它可能不会并改为执行 RVO,但它可以 使用它,这是重要的部分。
†或者其他一些用户声明的构造函数当然使该行合法。但上面是您显然询问的两个常见的编译器生成的。