问题 为什么C ++ destuctor会影响返回值优化的行为


我简化了我的代码如下。

#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;
}

我想在函数中利用RVO(返回值优化) NewIterator,但我收到以下错误:

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.comNewIterator() 应符合RVO的要求。但是,似乎编译器试图调用默认的复制构造函数 Iterator然后失败,因为 Iterator.buffer_ 是不可复制的。

好吧,令我惊讶的是,如果我删除了析构函数 Iterator 在L#13中,代码工作正常。

为什么析构函数会影响编译器的RVO行为?


1469
2017-12-08 17:12


起源



答案:


首先,在这种情况下忘记RVO。这是一个合法的优化,但即使它确实发生了,代码必须是合法的,没有它。

所以考虑到这一点,我们来看看

auto it = fa.NewIterator();

这一行试图构建一个新的 Iterator 从一个临时的 Iterator。为此,我们需要以下两种方法之一

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

现在在您发布的代码中,尝试使用隐式声明的 Iterator(const Iterator&); 将导致您显示的编译器错误,因为非静态成员的复制构造函数 buffer_ 无法编译。

第二个候选人没有生成因为 Iterator 有一个用户定义的析构函数。

如果删除用户定义的析构函数,编译器将生成移动构造函数 Iterator(Iterator&&); 并使用它,因为我们正在建立一个临时的。或者它可能不会和RVO相反,但它 可以 使用它,这是重要的部分。


或者其他一些用户声明的构造函数当然使该行合法。但上面是两个通常编译器生成的,你显然要问的。


11
2017-12-08 17:25



不太对劲。 Iterator(const Iterator&); 将被隐式声明并定义为默认(未删除)。但是,复制构造函数 std::vector<NoncopyableItem> 将无法实例化。 (std::vector<NoncopyableItem> 就成员可复制性的检查而言,它具有可访问的复制构造函数。)因此OP的错误。 - T.C.
@ T.C。谢谢,修好。然而,它将被定义为在N4140中从12.7 / 11删除,对吗? - Baum mit Augen
没有, Iterator复制构造函数实际上并未删除,因为名义上是矢量 确实有 一个可访问且未删除的复制构造函数。 static_assert(std::is_copy_constructible<Iterator>{}, ""); 将 不 火。隐含的定义 Iterator的复制构造函数触发了向量的复制构造函数的实例化,这会导致硬错误。 - T.C.
@ T.C。好,谢谢。现在是正确的吗? - Baum mit Augen
在我看来很好。 - T.C.


答案:


首先,在这种情况下忘记RVO。这是一个合法的优化,但即使它确实发生了,代码必须是合法的,没有它。

所以考虑到这一点,我们来看看

auto it = fa.NewIterator();

这一行试图构建一个新的 Iterator 从一个临时的 Iterator。为此,我们需要以下两种方法之一

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

现在在您发布的代码中,尝试使用隐式声明的 Iterator(const Iterator&); 将导致您显示的编译器错误,因为非静态成员的复制构造函数 buffer_ 无法编译。

第二个候选人没有生成因为 Iterator 有一个用户定义的析构函数。

如果删除用户定义的析构函数,编译器将生成移动构造函数 Iterator(Iterator&&); 并使用它,因为我们正在建立一个临时的。或者它可能不会和RVO相反,但它 可以 使用它,这是重要的部分。


或者其他一些用户声明的构造函数当然使该行合法。但上面是两个通常编译器生成的,你显然要问的。


11
2017-12-08 17:25



不太对劲。 Iterator(const Iterator&); 将被隐式声明并定义为默认(未删除)。但是,复制构造函数 std::vector<NoncopyableItem> 将无法实例化。 (std::vector<NoncopyableItem> 就成员可复制性的检查而言,它具有可访问的复制构造函数。)因此OP的错误。 - T.C.
@ T.C。谢谢,修好。然而,它将被定义为在N4140中从12.7 / 11删除,对吗? - Baum mit Augen
没有, Iterator复制构造函数实际上并未删除,因为名义上是矢量 确实有 一个可访问且未删除的复制构造函数。 static_assert(std::is_copy_constructible<Iterator>{}, ""); 将 不 火。隐含的定义 Iterator的复制构造函数触发了向量的复制构造函数的实例化,这会导致硬错误。 - T.C.
@ T.C。好,谢谢。现在是正确的吗? - Baum mit Augen
在我看来很好。 - T.C.