Хранение unique_ptr с пользовательским удалением на карте

Почему это не работает?

#include <map>
#include <memory>

void deleter(int* i) {
    delete i;
}

std::map<int, std::unique_ptr<int, decltype(&deleter)>> m;

void foo(int* i) {
    m[0] = std::unique_ptr<int, decltype(&deleter)>(i, &deleter);
}

Проверьте непонятную ошибку компиляции https://godbolt.org/z/Uhp9NO.

In file included from <source>:1:
In file included from /opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/map:61:
In file included from /opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/bits/stl_map.h:63:
/opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/tuple:1668:9: error: no matching constructor for initialization of 'std::unique_ptr<int, void (*)(int *)>'
        second(std::forward<_Args2>(std::get<_Indexes2>(__tuple2))...)
        ^
/opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/tuple:1655:9: note: in instantiation of function template specialization 'std::pair<const int, std::unique_ptr<int, void (*)(int *)> >::pair<int &&, 0>' requested here
      : pair(__first, __second,
        ^
/opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/ext/new_allocator.h:136:23: note: in instantiation of function template specialization 'std::pair<const int, std::unique_ptr<int, void (*)(int *)> >::pair<int &&>' requested here
        { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
                             ^
/opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/bits/alloc_traits.h:475:8: note: in instantiation of function template specialization '__gnu_cxx::new_allocator<std::_Rb_tree_node<std::pair<const int, std::unique_ptr<int, void (*)(int *)> > > >::construct<std::pair<const int, std::unique_ptr<int, void (*)(int *)> >, const std::piecewise_construct_t &, std::tuple<int &&>, std::tuple<> >' requested here
        { __a.construct(__p, std::forward<_Args>(__args)...); }
              ^
/opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/bits/stl_tree.h:637:23: note: in instantiation of function template specialization 'std::allocator_traits<std::allocator<std::_Rb_tree_node<std::pair<const int, std::unique_ptr<int, void (*)(int *)> > > > >::construct<std::pair<const int, std::unique_ptr<int, void (*)(int *)> >, const std::piecewise_construct_t &, std::tuple<int &&>, std::tuple<> >' requested here
              _Alloc_traits::construct(_M_get_Node_allocator(),
                             ^
/opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/bits/stl_tree.h:654:4: note: in instantiation of function template specialization 'std::_Rb_tree<int, std::pair<const int, std::unique_ptr<int, void (*)(int *)> >, std::_Select1st<std::pair<const int, std::unique_ptr<int, void (*)(int *)> > >, std::less<int>, std::allocator<std::pair<const int, std::unique_ptr<int, void (*)(int *)> > > >::_M_construct_node<const std::piecewise_construct_t &, std::tuple<int &&>, std::tuple<> >' requested here
          _M_construct_node(__tmp, std::forward<_Args>(__args)...);
          ^
/opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/bits/stl_tree.h:2414:19: note: in instantiation of function template specialization 'std::_Rb_tree<int, std::pair<const int, std::unique_ptr<int, void (*)(int *)> >, std::_Select1st<std::pair<const int, std::unique_ptr<int, void (*)(int *)> > >, std::less<int>, std::allocator<std::pair<const int, std::unique_ptr<int, void (*)(int *)> > > >::_M_create_node<const std::piecewise_construct_t &, std::tuple<int &&>, std::tuple<> >' requested here
        _Link_type __z = _M_create_node(std::forward<_Args>(__args)...);
                         ^
/opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/bits/stl_map.h:518:15: note: in instantiation of function template specialization 'std::_Rb_tree<int, std::pair<const int, std::unique_ptr<int, void (*)(int *)> >, std::_Select1st<std::pair<const int, std::unique_ptr<int, void (*)(int *)> > >, std::less<int>, std::allocator<std::pair<const int, std::unique_ptr<int, void (*)(int *)> > > >::_M_emplace_hint_unique<const std::piecewise_construct_t &, std::tuple<int &&>, std::tuple<> >' requested here
          __i = _M_t._M_emplace_hint_unique(__i, std::piecewise_construct,
                     ^
<source>:11:6: note: in instantiation of member function 'std::map<int, std::unique_ptr<int, void (*)(int *)>, std::less<int>, std::allocator<std::pair<const int, std::unique_ptr<int, void (*)(int *)> > > >::operator[]' requested here
    m[0] = std::unique_ptr<int, decltype(&deleter)>(i, &deleter);
     ^
/opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/bits/unique_ptr.h:191:12: note: candidate template ignored: substitution failure [with _Up = void (*)(int *)]: no type named 'type' in 'std::enable_if<false, void>'
        constexpr unique_ptr() noexcept
                  ^
/opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/bits/unique_ptr.h:204:2: note: candidate constructor template not viable: requires single argument '__p', but no arguments were provided
        unique_ptr(pointer __p) noexcept
        ^
/opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/bits/unique_ptr.h:236:12: note: candidate constructor template not viable: requires 1 argument, but 0 were provided
        constexpr unique_ptr(nullptr_t) noexcept : unique_ptr() { }
                  ^
/opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/bits/unique_ptr.h:255:2: note: candidate constructor template not viable: requires single argument '__u', but no arguments were provided
        unique_ptr(unique_ptr<_Up, _Ep>&& __u) noexcept
        ^
/opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/bits/unique_ptr.h:265:2: note: candidate constructor template not viable: requires single argument '__u', but no arguments were provided
        unique_ptr(auto_ptr<_Up>&& __u) noexcept;
        ^
/opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/bits/unique_ptr.h:241:7: note: candidate constructor not viable: requires single argument '__u', but no arguments were provided
      unique_ptr(unique_ptr&& __u) noexcept
      ^
/opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/bits/unique_ptr.h:394:7: note: candidate constructor not viable: requires 1 argument, but 0 were provided
      unique_ptr(const unique_ptr&) = delete;
      ^
/opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/bits/unique_ptr.h:215:7: note: candidate constructor not viable: requires 2 arguments, but 0 were provided
      unique_ptr(pointer __p,
      ^
/opt/compiler-explorer/gcc-8.2.0/lib/gcc/x86_64-linux-gnu/8.2.0/../../../../include/c++/8.2.0/bits/unique_ptr.h:227:7: note: candidate constructor not viable: requires 2 arguments, but 0 were provided
      unique_ptr(pointer __p,
      ^
1 error generated.
Compiler returned: 1

Ответ 1

Проблема заключается в том, что m[0] вызывает конструктор по умолчанию для std::unique_ptr<int, decltype(&deleter)>, который недоступен, поскольку он требует указателя делетера.

Fix:

struct Deleter {
    void operator()(int* i) { delete i; }
};

std::map<int, std::unique_ptr<int, Deleter>> m;

void foo(int* i) {
    m[0] = std::unique_ptr<int, Deleter>(i);
}

Это также более эффективно, чем std::unique_ptr<int, decltype(&deleter)> потому что ему не нужно хранить тот же самый указатель, который будет deleter в каждом экземпляре std::unique_ptr. Ie sizeof(std::unique_ptr<int, Deleter>) < sizeof(std::unique_ptr<int, decltype(&deleter)>).

Ответ 2

std::unique_ptr, при использовании пользовательской функции удаления, как и у вас, не является конструктивным по умолчанию. std::map::operator[] требует, чтобы тип значения карты был по умолчанию конструктивным. Это значит, что вы не можете использовать operator [] с этим типом карты.

Для того чтобы std::unique_ptr по умолчанию конструктивным, вам нужен делеттер, который удовлетворяет тем, где std::is_default_constructible<Deleter>::value is true а Deleter не является типом указателя.