简体   繁体   中英

Why does deque::erase() invoke assignment operator?

Just as the title says, why does deque invoke the assignment operator of the contained type during erase()? I can understand why vector might because elements in a vector are in contiguous memory, but since deque doesn't guarantee contiguous memory, why would it try to move its elements around when some of its elements are removed.

This code fails to compile because my Contained type's assignment operator is deleted and it doesn't have a move constructor.

#include <deque>

class Contained
{
public:
    Contained() = default;
    ~Contained() { }
    Contained(const Contained&) = delete;
    Contained& operator=(const Contained&) = delete;
};

class Container
{
public:
    Container()
    {
        for(int i = 0; i < 5; i++) { m_containerDS.emplace_back(); }
    }

    ~Container() { }

    void clear() 
    { 
        m_containerDS.erase(m_containerDS.begin(), m_containerDS.end()); 
    }

private:
    std::deque<Contained> m_containerDS;
};


int main()
{
    return 0;
}

The MSVC compiler emits this error message:

C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\xutility(2527): error C2280: 'Contained &Contained::operator =(const Contained &)' : attempting to reference a deleted function
1>          ConsoleApplication13.cpp(12) : see declaration of 'Contained::operator ='
1>          C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\xutility(2548) : see reference to function template instantiation '_BidIt2 std::_Move_backward<_BidIt1,_BidIt2>(_BidIt1,_BidIt1,_BidIt2,std::_Nonscalar_ptr_iterator_tag)' being compiled
1>          with
1>          [
1>              _BidIt2=std::_Deque_iterator<std::_Deque_val<std::_Deque_simple_types<Contained>>>
1>  ,            _BidIt1=std::_Deque_iterator<std::_Deque_val<std::_Deque_simple_types<Contained>>>
1>          ]
1>          C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\deque(1622) : see reference to function template instantiation '_BidIt2 std::_Move_backward<std::_Deque_iterator<std::_Deque_val<std::_Deque_simple_types<Contained>>>,std::_Deque_iterator<std::_Deque_val<std::_Deque_simple_types<Contained>>>>(_BidIt1,_BidIt1,_BidIt2)' being compiled
1>          with
1>          [
1>              _BidIt2=std::_Deque_iterator<std::_Deque_val<std::_Deque_simple_types<Contained>>>
1>  ,            _BidIt1=std::_Deque_iterator<std::_Deque_val<std::_Deque_simple_types<Contained>>>
1>          ]
1>          C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\deque(1601) : while compiling class template member function 'std::_Deque_iterator<std::_Deque_val<std::_Deque_simple_types<Contained>>> std::deque<Contained,std::allocator<_Ty>>::erase(std::_Deque_const_iterator<std::_Deque_val<std::_Deque_simple_types<_Ty>>>,std::_Deque_const_iterator<std::_Deque_val<std::_Deque_simple_types<_Ty>>>)'
1>          with
1>          [
1>              _Ty=Contained
1>          ]
1>          ConsoleApplication13.cpp(27) : see reference to function template instantiation 'std::_Deque_iterator<std::_Deque_val<std::_Deque_simple_types<Contained>>> std::deque<Contained,std::allocator<_Ty>>::erase(std::_Deque_const_iterator<std::_Deque_val<std::_Deque_simple_types<_Ty>>>,std::_Deque_const_iterator<std::_Deque_val<std::_Deque_simple_types<_Ty>>>)' being compiled
1>          with
1>          [
1>              _Ty=Contained
1>          ]
1>          ConsoleApplication13.cpp(31) : see reference to class template instantiation 'std::deque<Contained,std::allocator<_Ty>>' being compiled
1>          with
1>          [
1>              _Ty=Contained
1>          ]

Short answer: because

Type requirements

-T must meet the requirements of MoveAssignable.

Long answer: even though std::deque doesn't have a requirement to provide contiguous memory, it still has a requirement to provide a constant complextity operator[] . This is why it has to move elements.

This is because std::deque very often implemented as a ring buffer, which in its turn often implemented as single-piece memory buffer. That means when you remove elements from the deque it may need to move some elements if erased elements are not at either the end or the beginning of the sequence. Here is the illustration:

    V buffer begins here                    V buffer ends here
1. [ ] [.] [.] [.] [.] [.] [.] [.] [ ] [ ] [ ]
        ^first element          ^last element

2. [ ] [.] [.] [.] [.] [.] [.] [.] [ ] [ ] [ ]
                ^ you want to remove this element.

                <= these elements should be moved
                    V   V   V   V
3. [ ] [.] [.] [ ] [:] [:] [:] [:] [ ] [ ] [ ]
                ^ element have been removed.

Actually assignment operator only used if there is no move operator for the type. So if you add to your class the following line then everything compiles just fine:

Contained& operator=(Contained&&) = default;

UPDATE: It seems I was wrong as the most of the STL implementations now use some variants of the Dynamic Arrays , not ring buffers. Still, they are arrays and there is need to move elements if an element removed from the middle of the array.

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