为什么deque::erase()调用赋值运算符

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

本文关键字:调用 赋值运算符 erase deque 为什么      更新时间:2023-10-16

正如标题所说,为什么deque在擦除()过程中调用所包含类型的赋值运算符?我能理解为什么向量可能是因为向量中的元素在连续内存中,但既然deque不能保证连续内存,为什么当它的一些元素被移除时,它会试图移动它的元素。

此代码无法编译,因为我的Contained类型的赋值运算符已删除,并且它没有移动构造函数。

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

MSVC编译器发出以下错误消息:

C:Program Files (x86)Microsoft Visual Studio 12.0VCincludexutility(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.0VCincludexutility(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.0VCincludedeque(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.0VCincludedeque(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>          ]

简短回答:因为

类型要求

-T必须满足MoveAssignable的要求。

长答案:即使std::deque没有提供连续内存的要求,它仍然需要提供常数复杂的operator[]。这就是为什么它必须移动元素。

这是因为std::deque经常被实现为环形缓冲区,而环形缓冲区通常被实现为单件内存缓冲区。这意味着,当从deque中删除元素时,如果删除的元素不在序列的末尾或开头,则可能需要移动一些元素。下图:

    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.

实际分配运算符仅在类型没有移动运算符时使用。因此,如果你在类中添加以下行,那么一切都会很好地编译:

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

更新:我似乎错了,因为现在大多数STL实现都使用了动态数组的一些变体,而不是环形缓冲区。尽管如此,它们还是数组,如果元素从数组的中间移除,则需要移动元素。