2016-07-30 4 views
6

Je veux vraiment passer quelques unique_ptr s d'un std::set dans un autre:Pourquoi je ne peux pas std :: déplacer std :: unique_ptrs entre std :: sets?

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

Cependant, mon GCC 4.8.5 sur CentOS 7 est nettement malheureux:

[[email protected] ~]# 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; 
    ^

Qu'est-ce que Je dois faire pour que cela fonctionne?

+0

_ "Que dois-je faire pour que cela fonctionne? "_ Cela fonctionne-t-il avec une version plus récente de GCC? –

+0

@ πάνταῥεῖ: [Apparemment pas] (http://coliru.stacked-crooked.com/a/909023cd2804d25c) –

+0

Eh bien, essayer pourrait aider à distinguer c'est un bug du GCC, ou un problème plus fondamental, tant que vous êtes sûr d'avoir appliqué les normes conformes au code. –

Répondre

12

Vous ne pouvez pas faire ceci en C++ 14 puisque les éléments d'un ensemble sont const. Puisque le déplacement est une opération de modification, vous devez avoir un moyen d'accéder aux éléments et il n'y a aucun moyen de le faire.

Mais vous serez en mesure de le faire en 17 C++ avec la nouvelle fonction de membre merge():

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

De même, il y aura une fonction de membre extract() qui enlèvera et vous donner non const accès à un seul noeud.


Bien sûr, vous pouvez toujours utiliser un type comme (avec h/t par 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 
    } 
}; 

Passer de p est maintenant sûr (il est mutable) et aussi longtemps que vous juste déplacer (et non reset()) et mettre en œuvre la commande en termes de pointeur brut, alors vous devez également conserver la mise en ordre. Vous devez stocker deux pointeurs maintenant, mais cela devrait éviter UB dans C++ 11.

+0

Mais je retire les éléments de l'ensemble. Pourquoi l'immutabilité clé devrait-elle affecter cela? –

+1

@Lightness Vous n'avez simplement pas d'accès non ''const'. – Barry

+0

Non, mais la bibliothèque standard le fait, et je demande à la bibliothèque standard de déplacer les éléments. –

4

Les éléments d'un ensemble std :: sont const, principalement parce qu'un ensemble std :: est ordonné par la valeur des éléments. Changer les valeurs pourrait changer l'ordre qui, en conséquence, corrompra la représentation interne de std :: set.

Déplacer un std :: unique_ptr d'un std :: set rendra certainement std :: set inutilisable.

0

est ici une façon de le faire en utilisant 11 C++ en g ++ 4.8.2:

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

Ceci affichera quelque chose comme ce qui suit:

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 } 
+0

Mais ... l'UB !! –

+0

Il n'y a pas de comportement indéfini (UB) ici parce que nous ne faisons que lancer const sur un std :: unique_ptr afin d'utiliser std :: move et destination.insert (std :: unique_ptr &&) ... tout cela sont bien définis. De plus, aucun appel à la source ne peut déclencher un réordonnancement de ses éléments entre le std :: move de son premier élément std :: unique_ptr à l'appel source.clear(). Par conséquent, tout comportement est défini. –

+2

Qu'est-ce qui est bien défini à propos de l'exécution d'une opération de mutation sur un objet que vous avez promis de ne pas muter? –