【问题标题】:Why can't I std::move std::unique_ptrs between std::sets?为什么我不能在 std::sets 之间 std::move std::unique_ptrs?
【发布时间】:2016-12-05 08:50:59
【问题描述】:

我真的想将一些unique_ptrs 从一个std::set 移到另一个:

#include <memory>
#include <algorithm>
#include <set>

int main()
{
   std::set<std::unique_ptr<int>> a;
   std::set<std::unique_ptr<int>> b;

   a.insert({0, std::unique_ptr<int>(new int(42))});

   std::move(a.begin(), a.end(), std::inserter(b, b.end()));
}

但是,我在 CentOS 7 上的 GCC 4.8.5 显然不满意:

[root@localhost ~]# g++ test.cpp -std=c++11 -o test
In file included from /usr/include/c++/4.8.2/set:60:0,
                 from test.cpp:2:
/usr/include/c++/4.8.2/bits/stl_tree.h: In instantiation of ‘std::_Rb_tree_node<_Val>::_Rb_tree_node(_Args&& ...) [with _Args = {const std::unique_ptr<int, std::default_delete<int> >&}; _Val = std::unique_ptr<int>]’:
/usr/include/c++/4.8.2/ext/new_allocator.h:120:4:   required from ‘void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = std::_Rb_tree_node<std::unique_ptr<int> >; _Args = {const std::unique_ptr<int, std::default_delete<int> >&}; _Tp = std::_Rb_tree_node<std::unique_ptr<int> >]’
/usr/include/c++/4.8.2/bits/alloc_traits.h:254:4:   required from ‘static typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type std::allocator_traits<_Alloc>::_S_construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = std::_Rb_tree_node<std::unique_ptr<int> >; _Args = {const std::unique_ptr<int, std::default_delete<int> >&}; _Alloc = std::allocator<std::_Rb_tree_node<std::unique_ptr<int> > >; typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type = void]’
/usr/include/c++/4.8.2/bits/alloc_traits.h:393:57:   required from ‘static decltype (_S_construct(__a, __p, (forward<_Args>)(std::allocator_traits::construct::__args)...)) std::allocator_traits<_Alloc>::construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = std::_Rb_tree_node<std::unique_ptr<int> >; _Args = {const std::unique_ptr<int, std::default_delete<int> >&}; _Alloc = std::allocator<std::_Rb_tree_node<std::unique_ptr<int> > >; decltype (_S_construct(__a, __p, (forward<_Args>)(std::allocator_traits::construct::__args)...)) = <type error>]’
/usr/include/c++/4.8.2/bits/stl_tree.h:408:36:   required from ‘std::_Rb_tree_node<_Val>* std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_create_node(_Args&& ...) [with _Args = {const std::unique_ptr<int, std::default_delete<int> >&}; _Key = std::unique_ptr<int>; _Val = std::unique_ptr<int>; _KeyOfValue = std::_Identity<std::unique_ptr<int> >; _Compare = std::less<std::unique_ptr<int> >; _Alloc = std::allocator<std::unique_ptr<int> >; std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Link_type = std::_Rb_tree_node<std::unique_ptr<int> >*]’
/usr/include/c++/4.8.2/bits/stl_tree.h:1023:66:   required from ‘std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_insert_(std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Base_ptr, std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Base_ptr, _Arg&&) [with _Arg = const std::unique_ptr<int>&; _Key = std::unique_ptr<int>; _Val = std::unique_ptr<int>; _KeyOfValue = std::_Identity<std::unique_ptr<int> >; _Compare = std::less<std::unique_ptr<int> >; _Alloc = std::allocator<std::unique_ptr<int> >; std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator = std::_Rb_tree_iterator<std::unique_ptr<int> >; std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_Base_ptr = std::_Rb_tree_node_base*]’
/usr/include/c++/4.8.2/bits/stl_tree.h:1482:33:   required from ‘std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_insert_unique_(std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::const_iterator, _Arg&&) [with _Arg = const std::unique_ptr<int>&; _Key = std::unique_ptr<int>; _Val = std::unique_ptr<int>; _KeyOfValue = std::_Identity<std::unique_ptr<int> >; _Compare = std::less<std::unique_ptr<int> >; _Alloc = std::allocator<std::unique_ptr<int> >; std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::iterator = std::_Rb_tree_iterator<std::unique_ptr<int> >; std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::const_iterator = std::_Rb_tree_const_iterator<std::unique_ptr<int> >]’
/usr/include/c++/4.8.2/bits/stl_tree.h:1722:37:   required from ‘void std::_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::_M_insert_unique(_II, _II) [with _InputIterator = const std::unique_ptr<int>*; _Key = std::unique_ptr<int>; _Val = std::unique_ptr<int>; _KeyOfValue = std::_Identity<std::unique_ptr<int> >; _Compare = std::less<std::unique_ptr<int> >; _Alloc = std::allocator<std::unique_ptr<int> >]’
/usr/include/c++/4.8.2/bits/stl_set.h:518:4:   required from ‘void std::set<_Key, _Compare, _Alloc>::insert(_InputIterator, _InputIterator) [with _InputIterator = const std::unique_ptr<int>*; _Key = std::unique_ptr<int>; _Compare = std::less<std::unique_ptr<int> >; _Alloc = std::allocator<std::unique_ptr<int> >]’
/usr/include/c++/4.8.2/bits/stl_set.h:530:9:   required from ‘void std::set<_Key, _Compare, _Alloc>::insert(std::initializer_list<_Tp>) [with _Key = std::unique_ptr<int>; _Compare = std::less<std::unique_ptr<int> >; _Alloc = std::allocator<std::unique_ptr<int> >]’
test.cpp:9:49:   required from here
/usr/include/c++/4.8.2/bits/stl_tree.h:140:49: 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>]’
    _M_value_field(std::forward<_Args>(__args)...) { }
                                                 ^
In file included from /usr/include/c++/4.8.2/memory:81:0,
                 from test.cpp:1:
/usr/include/c++/4.8.2/bits/unique_ptr.h:273:7: error: declared here
       unique_ptr(const unique_ptr&) = delete;
       ^

我需要做什么才能完成这项工作?

【问题讨论】:

  • “我需要做些什么来完成这项工作?” 它是否适用于较新的 GCC 版本?
  • @πάνταῥεῖ:Apparently not
  • 好吧,只要您确定应用了符合标准的代码,尝试一下有助于区分它是 GCC 错误还是更根本的问题。
  • 我不明白为什么我是唯一一个对此投赞成票的人。
  • @Barry:我也没有,因为分数是 2 :)

标签: c++ c++11 std move-semantics unique-ptr


【解决方案1】:

在 C++14 中你基本上不能这样做,因为集合的元素是 const。由于移动是一种修改操作,因此您需要一些方法来获得对元素的非const 访问权限,而根本没有办法做到这一点。

但是您能够在 C++17 中使用新的 merge() 成员函数来做到这一点:

b.merge(std::move(a));

同样,将有一个 extract() 成员函数,该函数将删除并授予您对单个节点的非 const 访问权限。


当然,您总是可以使用类似的类型(使用 h/t by T.C.):

struct Hack {
    mutable std::unique_ptr<T> p;
    T* raw_ptr;

    bool operator<(Hack const& h) const {
        // implemented in terms of the raw ptr
        // not the unique ptr
    }
};

p 移动现在是安全的(它是可变的),只要您只是 移动(而不是reset())并根据原始指针实现排序,那么您应该也保留集合顺序。您现在必须存储两个指针,但这应该避免 C++11 中的 UB。

【讨论】:

  • @Lightness 你根本没有非const 访问它们。
  • @LightnessRacesinOrbit:我不确定你在说什么。当然,它们仍然存在于集合中。 std::move 不会从它所操作的容器中移除元素。它移动它们,我敢肯定,你很清楚,这是一个修改操作。
  • @BenjaminLindley: "std::move 不会从它所操作的容器中删除元素" 哦....是的,这就是原因。为什么标准库中所有名为“move”的东西都被命名为badly? ;) 好的,所以是的,确实,我忘记了我是在范围而不是容器上操作。德拉特。
  • @BenjaminLindley 考虑写这个作为答案。
  • @LightnessRacesinOrbit: extract将从set 中删除一个节点,并为您提供对该节点的非常量访问权限,以便您可以从中移动:en.cppreference.com/w/cpp/container/set/extract
【解决方案2】:

std::set 的元素是const,主要是因为std::set 是按元素的值排序的。更改值可能会更改顺序,因此会破坏 std::set 的内部表示。

将 std::unique_ptr 移出 std::set 肯定会使 std::set 不可用。

【讨论】:

    【解决方案3】:

    这是在 g++ 4.8.2 中使用 C++11 执行此操作的一种方法:

    #include <iostream>
    #include <set>
    #include <memory>
    #include <utility>
    
    // One possible solution:
    template <typename Type>
    void move_set_unique_ptr( 
        std::set<std::unique_ptr<Type>> & source,
        std::set<std::unique_ptr<Type>> & destination
    ) {
        for ( const std::unique_ptr<Type> & source_unique_ptr : source ) {
            destination.insert(
                std::move( const_cast<std::unique_ptr<Type> &>( source_unique_ptr ) )
            );
        }
        source.clear();
    }
    
    // Not part of a solution ... just for use with std::cout:
    template <typename Type>
    std::ostream & operator << (
        std::ostream & o,
        const std::set<std::unique_ptr<Type>> & s
    );
    
    int main() {
        using type = int;
    
        std::set<std::unique_ptr<type>> a;
        std::set<std::unique_ptr<type>> b;
    
        // std::make_unique<T> is not available in C++11
    
        a.insert( nullptr );
        a.insert( std::unique_ptr<type>( new type{ 62 } ) );
        a.insert( std::unique_ptr<type>( new type{ 42 } ) );
        a.insert( std::unique_ptr<type>( new type{ 22 } ) );
    
        b.insert( std::unique_ptr<type>( new type{ 41 } ) );
        b.insert( std::unique_ptr<type>( new type{ 42 } ) );
        b.insert( std::unique_ptr<type>( new type{ 43 } ) );
        b.insert( nullptr );
    
        std::cout << "a: " << a << '\n';
        std::cout << "b: " << b << '\n';
    
        move_set_unique_ptr(a, b);
    
        std::cout << "a: " << a << '\n';
        std::cout << "b: " << b << '\n';
    }
    
    // Not part of a solution ... just for use with std::cout:
    template <typename Type>
    std::ostream & operator << (
        std::ostream & o,
        const std::set<std::unique_ptr<Type>> & s
    ) {
        auto i = std::begin( s );
        auto end = std::end( s );
        o << '{';
        if ( i != end ) {
            auto print = [ &o, &i ]() {
                o << i->get() << ": " << ( *i == nullptr ? Type{} : **i );
            };
            o << ' '; print();
            for ( ++i; i != end; ++i ) {
                o << ", "; print();
            }
            o << ' ';
        }
        o << '}';
        return o;
    }
    

    这将输出如下内容:

    a: { 0: 0, 0x8f3c50: 62, 0x8f3ca0: 42, 0x8f3cf0: 22 }
    b: { 0: 0, 0x8f3d40: 41, 0x8f3d90: 42, 0x8f3de0: 43 }
    a: {}
    b: { 0: 0, 0x8f3c50: 62, 0x8f3ca0: 42, 0x8f3cf0: 22, 0x8f3d40: 41, 0x8f3d90: 42, 0x8f3de0: 43 }
    

    【讨论】:

    • 这里没有未定义的行为 (UB),因为我们只是在 std::unique_ptr 上强制转换 const 以便使用 std::move 和 destination.insert( std::unique_ptr && ) ...所有这些都定义明确。此外,没有任何对 source 的调用可以触发其第一个 std::unique_ptr 元素的 std::move 到 source.clear() 调用之间的元素重新排序。因此,定义了所有行为。
    • 对您承诺不会变异的对象执行变异操作有什么明确定义?
    猜你喜欢
    • 2019-07-09
    • 2014-02-16
    • 2017-06-11
    • 1970-01-01
    • 1970-01-01
    • 2020-12-23
    • 1970-01-01
    • 2012-07-28
    相关资源
    最近更新 更多