Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to move an std::unique_ptr<> from one STL container to another?

Tags:

c++

c++11

stl

Problem

I have a template container MyContainer<std::unique_ptr<Foo>> which has a std::deque<T> and a std::vector<T> member.

Inside method, send_to_purgatory_if( predicate ), I would like to look at all items in m_taskdq and move items from m_taskdq to m_purgatory, if the predicate evaluates to true.

Issues

I have two issues that I'm struggling with:

  • my iterator it gets trashed if I remove items from m_taskdq from inside the loop
  • I am worried about the state of the std::unique_ptr<> if I do the move in two steps (problem lines 1 and 2 - by line 2, I think the std::unique_ptr<> pointed to by it is undefined?)

How should I fix this code?

    template <typename T>
    class MyContainer
    {
      typedef std::function<bool(T&)>  PREDICATE;
    
      void send_to_purgatory_if( PREDICATE p )
      {
// bad code -------------------------------------
        for( auto it=m_taskdq.begin(); it!=m_taskdq.end(); ++it )
        {
          if ( p( *it ) )
          {
            m_purgatory.emplace_back( move( *it ));  // problem line 1
            m_taskdq.erase( it );                    // problem line 2
          }
        }
// end bad code ---------------------------------
      }
    
      std::deque<  T >  m_taskdq;                                                     
      std::vector< T >  m_purgatory;
    };
like image 870
kfmfe04 Avatar asked Dec 13 '22 04:12

kfmfe04


1 Answers

This is really a C++98 question, with a red-herring concerning move semantics. The first thing to ask is how to do this in C++98:

std::deque::erase(iterator) returns an iterator that refers to the element after the one erased. So get that working first:

 void send_to_purgatory_if( PREDICATE p )
  {
    for( auto it=m_taskdq.begin(); it!=m_taskdq.end();)
    {
      if ( p( *it ) )
      {
        m_purgatory.emplace_back(*it);
        it = m_taskdq.erase(it);
      }
      else
        ++it;
    }
  }

And now it is easy to make it work with C++11 move semantics:

 void send_to_purgatory_if( PREDICATE p )
  {
    for( auto it=m_taskdq.begin(); it!=m_taskdq.end();)
    {
      if ( p( *it ) )
      {
        m_purgatory.emplace_back(std::move(*it));
        it = m_taskdq.erase(it);
      }
      else
        ++it;
    }
  }

The unique_ptr moved from in taskdq becomes a null unique_ptr after the emplace_back, and then it gets erased in the next line. No harm, no foul.

When there is an erase, the return from the erase does a good job at incrementing the iterator. And when there is no erase, a normal iterator increment is in order.

like image 73
Howard Hinnant Avatar answered Dec 31 '22 00:12

Howard Hinnant