简体   繁体   中英

Class with explicit destructor and std::unique_ptr<> member can't be used in std::vector<>?

This code

#include <memory>
#include <vector>

class Foo
{
public:
    ~Foo()
    {
    }

    std::unique_ptr<int> bar;
};

int main()
{
    std::vector<Foo> foos;
    foos.emplace_back();
}

yields the following error message in g++:

In file included from /usr/include/c++/4.8/memory:64:0,
                 from main.cpp:1:
/usr/include/c++/4.8/bits/stl_construct.h: In instantiation of ‘void std::_Construct(_T1*, _Args&& ...) [with _T1 = Foo; _Args = {Foo}]’:
/usr/include/c++/4.8/bits/stl_uninitialized.h:75:53:   required from ‘static _ForwardIterator std::__uninitialized_copy<_TrivialValueTypes>::__uninit_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = std::move_iterator<Foo*>; _ForwardIterator = Foo*; bool _TrivialValueTypes = false]’
/usr/include/c++/4.8/bits/stl_uninitialized.h:117:41:   required from ‘_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = std::move_iterator<Foo*>; _ForwardIterator = Foo*]’
/usr/include/c++/4.8/bits/stl_uninitialized.h:258:63:   required from ‘_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = std::move_iterator<Foo*>; _ForwardIterator = Foo*; _Tp = Foo]’
/usr/include/c++/4.8/bits/stl_uninitialized.h:281:69:   required from ‘_ForwardIterator std::__uninitialized_move_if_noexcept_a(_InputIterator, _InputIterator, _ForwardIterator, _Allocator&) [with _InputIterator = Foo*; _ForwardIterator = Foo*; _Allocator = std::allocator<Foo>]’
/usr/include/c++/4.8/bits/vector.tcc:415:43:   required from ‘void std::vector<_Tp, _Alloc>::_M_emplace_back_aux(_Args&& ...) [with _Args = {}; _Tp = Foo; _Alloc = std::allocator<Foo>]’
/usr/include/c++/4.8/bits/vector.tcc:101:54:   required from ‘void std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {}; _Tp = Foo; _Alloc = std::allocator<Foo>]’
main.cpp:17:23:   required from here
/usr/include/c++/4.8/bits/stl_construct.h:75:7: error: use of deleted function ‘Foo::Foo(const Foo&)’
     { ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
       ^
main.cpp:4:7: note: ‘Foo::Foo(const Foo&)’ is implicitly deleted because the default definition would be ill-formed:
 class Foo
       ^
main.cpp:4: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>]’
In file included from /usr/include/c++/4.8/memory:81:0,
                 from main.cpp:1:
/usr/include/c++/4.8/bits/unique_ptr.h:273:7: error: declared here
       unique_ptr(const unique_ptr&) = delete;

yet if I remove the ~Foo() implementation (and declaration) it compiles just fine. The same behaviour can be observed would main() look like this:

int main()
{
    auto f = Foo();
}
  • Why is the copy constructor of Foo being called anyway? Shouldn't everything here happen via move semantics?

  • Why wouldn't it (the copy constructor) be implicitly deleted if I remove the destructor?

The explicit destructor declaration (and definition) suppresses the implicit declaration of the move constructor and the move assignment operator. Thus the only thing that can be used is the copy constructor (which is implicitly declared no matter what) - but it's defined as deleted because unique_ptr s are not copyable. Hence the error.

Explicitly default them - and the default constructor too since you have a user-declared constructor now:

Foo() = default;
Foo(Foo&&) = default;
Foo& operator=(Foo&&) = default;

Why wouldn't it (the copy constructor) be implicitly deleted if I remove the destructor?

It's still implicitly deleted. But since now a move constructor is implicitly declared, the move constructor can used for moving, and the copy constructor simply isn't used.


Relevant standard quote (§12.8 [class.copy]/p9, 20, emphasis added):

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, and
  • X does not have a user-declared destructor .

20 If the definition of a class X does not explicitly declare a move assignment operator, 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 move constructor,
  • X does not have a user-declared copy assignment operator, and
  • X does not have a user-declared destructor .

Foo is not copyable since it has a member unique_ptr . Foo is also not movable because declaration of a user-provided destructor suppresses the implicit generation of the default move constructor / assignment. If you want Foo to be movable default the move constructor/assignment so the compiler will generate them for you:

class Foo
{
public:
    ~Foo()
    {
    }

    Foo(Foo&&) = default;
    Foo& operator=(Foo&&) = default;

    std::unique_ptr<int> bar;
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM