Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Providing an (empty) user-defined destructor causes compilation error

Code which compiles perfectly fine (on GCC 4.7.2) when I do not have a user-defined destructor, produces errors when even an empty user-defined destructor is provided:

#include <memory>

class Test
{
   std::unique_ptr<int> val;
};

template <typename Type>
class B
{
public:
   //destructor:
   // if I comment this out, the code compiles just fine:
   ~B() { }

private:
   Test a;
};

int main()
{
   auto s = B<int>();
}

The salient points in the error produced when the destructor is not commented out are:

  • The copy constructor for Test does not exist, and was not implicitly created because it would be ill formed
  • Something tried to use the deleted copy constructor for unique_ptr.

The complete error output, for anyone interested, is at the bottom of this post.

I am of aware that unique_ptr cannot be copy constructed (unless the argument is an rvalue), and thus that it is not possible for the compiler to generate a valid implicit copy constructor for the class Test.

What I cannot figure out is why defining even an empty destructor should suddenly require these copy facilities. Obviously, when things like unique_ptrs are used, this isn't possible to provide.

Would anyone here be able to enlighten me as to why this is the case?

Complete error output when the destructor is not commented out:

In file included from /usr/include/c++/4.7/list:64:0,
                 from ../../Dropbox/Programming/C++/test/main.cpp:2:
/usr/include/c++/4.7/bits/stl_list.h: In instantiation of 'std::_List_node<_Tp>::_List_node(_Args&& ...) [with _Args = {const Test&}; _Tp = Test]':
/usr/include/c++/4.7/ext/new_allocator.h:110:4:   required from 'void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = std::_List_node<Test>; _Args = {const Test&}; _Tp = std::_List_node<Test>]'
/usr/include/c++/4.7/bits/stl_list.h:503:8:   required from 'std::list<_Tp, _Alloc>::_Node* std::list<_Tp, _Alloc>::_M_create_node(_Args&& ...) [with _Args = {const Test&}; _Tp = Test; _Alloc = std::allocator<Test>; std::list<_Tp, _Alloc>::_Node = std::_List_node<Test>]'
/usr/include/c++/4.7/bits/stl_list.h:1533:63:   required from 'void std::list<_Tp, _Alloc>::_M_insert(std::list<_Tp, _Alloc>::iterator, _Args&& ...) [with _Args = {const Test&}; _Tp = Test; _Alloc = std::allocator<Test>; std::list<_Tp, _Alloc>::iterator = std::_List_iterator<Test>]'
/usr/include/c++/4.7/bits/stl_list.h:997:9:   required from 'void std::list<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = Test; _Alloc = std::allocator<Test>; std::list<_Tp, _Alloc>::value_type = Test]'
/usr/include/c++/4.7/bits/stl_list.h:1466:6:   required from 'void std::list<_Tp, _Alloc>::_M_initialize_dispatch(_InputIterator, _InputIterator, std::__false_type) [with _InputIterator = std::_List_const_iterator<Test>; _Tp = Test; _Alloc = std::allocator<Test>]'
/usr/include/c++/4.7/bits/stl_list.h:582:9:   required from 'std::list<_Tp, _Alloc>::list(const std::list<_Tp, _Alloc>&) [with _Tp = Test; _Alloc = std::allocator<Test>; std::list<_Tp, _Alloc> = std::list<Test>]'
../../Dropbox/Programming/C++/test/main.cpp:11:7:   required from here
/usr/include/c++/4.7/bits/stl_list.h:115:71: error: use of deleted function 'Test::Test(const Test&)'
../../Dropbox/Programming/C++/test/main.cpp:5:7: note: 'Test::Test(const Test&)' is implicitly deleted because the default definition would be ill-formed:
../../Dropbox/Programming/C++/test/main.cpp:5:7: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>; std::unique_ptr<_Tp, _Dp> = std::unique_ptr<int>]'
In file included from /usr/include/c++/4.7/memory:86:0,
                 from ../../Dropbox/Programming/C++/test/main.cpp:3:
/usr/include/c++/4.7/bits/unique_ptr.h:262:7: error: declared here
like image 656
jsdw Avatar asked Apr 24 '13 13:04

jsdw


2 Answers

If you define ~B(), this suppresses the move constructor of B, so the compiler tries to generate a copy constructor but fails because unique_ptr is not copy-constructible.

If you omit ~B(), the move constructor of B is generated and is used in main.

You can request the automatically generated move constructor:

B(B &&) = default;

This is a feature in the standard for backwards compatibility with C++03 code; per the Rule of Three, code that writes its own destructor (etc.) is assumed to be managing its own resources so automatically generating a move constructor would not be appropriate unless explicitly requested.

like image 131
ecatmur Avatar answered Sep 19 '22 12:09

ecatmur


Well, you said it yourself; when you provide a user-defined destructor, you inhibit the compiler's ability to generate things like an implicit move constructor. The contents of your user-defined destructor (be it empty or otherwise) are completely irrelevant.

[C++11: 12.7/9]: If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if:

  • X does not have a user-declared copy constructor,
  • X does not have a user-declared copy assignment operator,
  • X does not have a user-declared move assignment operator,
  • X does not have a user-declared destructor, and
  • the move constructor would not be implicitly defined as deleted.

[ Note: When the move constructor is not implicitly declared or explicitly supplied, expressions that otherwise would have invoked the move constructor may instead invoke a copy constructor. —end note ]

The text in the note points out the exact case that you are seeing, leading to a compilation failure because unique_ptr cannot be copied; although you're not copying it, and although the initialisation of s may not require a copy/move, the operation is still required to be available (per [C++11: 12.8/31-32]).

like image 36
Lightness Races in Orbit Avatar answered Sep 21 '22 12:09

Lightness Races in Orbit