Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why C++ destuctor affect the behavior of return value optimization

I have simplified my code as follows.

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

I want to leverage the RVO (return value optimization) in function NewIterator, but I got the following error:

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

According to cppreference.com, NewIterator() should meet the requirement of RVO. However, it seems that the compiler trys to call the default copy constructor of Iterator, then fails because Iterator.buffer_ is noncopyable.

Well, to my suprise, if I delete the destructor of Iterator in L#13, the code works fine.

Why does the destructor affect the RVO behavior of the compiler?

like image 580
Yangyang Zhang Avatar asked Dec 08 '15 17:12

Yangyang Zhang


People also ask

Does C have return value optimization?

> Note also that C doesn't have return-value-optimization, hence all your struct-returning functions will cause a call to memcpy (won't happen when compiled in C++ mode of course).

Is return value optimization guaranteed?

Compilers often perform Named Return Value Optimization (NRVO) in such cases, but it is not guaranteed.

Is the destructor automatically called?

A destructor is a member function that is invoked automatically when the object goes out of scope or is explicitly destroyed by a call to delete . A destructor has the same name as the class, preceded by a tilde ( ~ ).

How do you call a destructor in c++?

A destructor is called for a class object when that object passes out of scope or is explicitly deleted. A destructor is a member function with the same name as its class prefixed by a ~ (tilde). For example: class X { public: // Constructor for class X X(); // Destructor for class X ~X(); };


1 Answers

First of all, forget about RVO in this context. It is a legal optimization, but even when it does happen, the code must be legal without it.

So with this in mind, we look at

auto it = fa.NewIterator();

This line attempts to construct a new Iterator from a temporary Iterator. For this to work, we need either of the following two:

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

Now in the code you posted, trying to use the implicitly declared Iterator(const Iterator&); will result in the compiler error you showed because the copy constructor of the non-static member buffer_ fails to compile.

The second candidate is not generated because Iterator has a user defined destructor.

If you remove the user defined destructor, the compiler will generate the move constructor Iterator(Iterator&&); and use it as we are constructing from a temporary. Or maybe it won't and do RVO instead, but it could use it, and that is the important part.


Or some other user declared constructor that makes the line legal of course. But above are the two commonly compiler generated ones you apparently ask about.

like image 80
Baum mit Augen Avatar answered Sep 28 '22 06:09

Baum mit Augen