这里是 GCC 4.4.7 的 vector::push_back 的实现;我添加了一些 cmets:
void
push_back(const value_type& __x)
{
// Do we still have some space left in the vector?
if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
{
// Yes, some space left. Just construct new element in the existing space.
this->_M_impl.construct(this->_M_impl._M_finish, __x);
++this->_M_impl._M_finish;
}
else
// No space left, we need to allocate.
_M_insert_aux(end(), __x);
}
如果没有空格,这里使用的函数是_M_insert_aux。请注意,我们提供 end() 作为参数。函数_M_insert_aux 可以插入到向量内的any 位置。这很重要:如果我们在结尾以外的任何地方插入,我们需要移动元素。前任。如何在位置 3 插入d:
+-+-+-+-+-+-+-+
|a|b|c|e|f| | |
+-+-+-+-+-+-+-+
move f one right:
+-+-+-+-+-+-+-+
|a|b|c|e|-|f| |
+-+-+-+-+-+-+-+
move e one right:
+-+-+-+-+-+-+-+
|a|b|c|-|e|f| |
+-+-+-+-+-+-+-+
place d:
+-+-+-+-+-+-+-+
|a|b|c|d|e|f| |
+-+-+-+-+-+-+-+
第一个移动操作是移动构造(移动到没有对象的位置)。第二个移动操作是移动分配(您移动到有移动对象的位置)。放置也是一个move-assignment,之前在同一个地方也有过对象。
这是_M_insert_aux 的开头,有点简化:
template<typename _Tp, typename _Alloc>
template<typename... _Args>
void
vector<_Tp, _Alloc>::
_M_insert_aux(iterator __position, _Args&&... __args)
{
// if there's space left for the new element
if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
{
// move the last element over by 1 (move-construct)
this->_M_impl.construct(this->_M_impl._M_finish,
_GLIBCXX_MOVE(*(this->_M_impl._M_finish
- 1)));
++this->_M_impl._M_finish;
// safety copy of argument, in case argument refers into the vector
_Tp __x_copy = __x;
// move-assign all elements over until the __position
_GLIBCXX_MOVE_BACKWARD3(__position.base(),
this->_M_impl._M_finish - 2,
this->_M_impl._M_finish - 1);
// move-assign new element to correct position
*__position = _Tp(std::forward<_Args>(__args)...);
}
它实现了上面描述的算法。请注意,这是我们确实有足够空间的情况。在push_back 中,如果有剩余空间,我们永远不会调用_M_insert_aux。但是,此代码路径仍必须编译。
地图在树中创建节点。这些节点是pair<const key, value>。为什么是常量?因为节点在树中的位置取决于键的值。如果更改该键,则元素位于错误的位置,这会破坏数据结构不变量。
因此,MyMap::value_type 也是 pair<const key, value>,在您的情况下为 pair< const pair<uint,ulong>, pair<int,int> >。
无论如何,pair<const key, value> 是不可分配的,因为您无法更改第一个元素。因此,_M_insert_aux 中的 move-assignments 无法编译。
gcc 4.7 实现push_back 的方式不是最优的,因为_M_insert_aux 中的代码路径存在并且强制值类型必须是可移动赋值的。从理论上讲,这不是必需的:如果仅在末尾插入,则插入到还没有对象的位置。因此,您调用的是(移动)构造函数,而不是(移动)赋值运算符。
C++11 标准也不允许编译器/库提出该要求。因此,较新的 GCC 版本具有更智能的 push_back 实现,从而避免尝试从 push_back 编译移动赋值。
请注意,您还会遇到类模板的怪癖:类模板就像生成类的工厂。它们的成员函数体是按需生成的。也就是说,您必须在代码中的某处调用vector<yourtype>::push_back 才能生成此函数(“实例化”)。 vector<yourtype>::insert 永远不会编译,因为它尝试对 pair<const key, value> 进行相同的分配。不过只要不打vector<yourtype>::insert,没关系。
因此,对于不可赋值的类型,您仍然可以使用vector<yourtype>。但是你只能调用那些不使用赋值的成员函数。在 GCC 4.4 中,push_back 编译一个赋值,即使它从未被使用过。在更新的版本中,push_back 不直接或间接使用赋值。