【问题标题】:Vector in GCC 4.4.7 Vs GCC 4.9.2GCC 4.4.7 与 GCC 4.9.2 中的向量
【发布时间】:2020-12-22 20:30:48
【问题描述】:

我有如下代码。

void func() 
{
    typedef map<pair<uint,ulong>, pair<int,int> > MyMap;
    MyMap myMap;
    vector<MyMap::value_type> mapVector;
    //...
    for(MyMap::iterator it = myMap.begin(); it != myMap.end(); it++)
    {
        //if(...)
            mapVector.push_back(*it);
    }
    //...
}

使用 GCC 4.9.2 中的 -std=gnu++11 选项可以正常编译,请参阅 here,但使用 GCC 4.4.7 中的 -std=gnu++0x 选项编译失败,请参阅 here。失败在 const 对上的 operator= 中。那么它如何与 GCC 4.9.2 一起工作呢?有人可以阐明这种差异吗?提前致谢!

【问题讨论】:

  • 4.4.7 版是否实现了移动语义?
  • 来自 4.4 的发行说明:“改进了对即将推出的 ISO C++ 标准 C++0x 的实验性支持”。注意“实验”和“即将”。

标签: c++ c++11 stl std


【解决方案1】:

这里是 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&lt;const key, value&gt;。为什么是常量?因为节点在树中的位置取决于键的值。如果更改该键,则元素位于错误的位置,这会破坏数据结构不变量。

因此,MyMap::value_type 也是 pair&lt;const key, value&gt;,在您的情况下为 pair&lt; const pair&lt;uint,ulong&gt;, pair&lt;int,int&gt; &gt;

无论如何,pair&lt;const key, value&gt; 是不可分配的,因为您无法更改第一个元素。因此,_M_insert_aux 中的 move-assignments 无法编译。


gcc 4.7 实现push_back 的方式不是最优的,因为_M_insert_aux 中的代码路径存在并且强制值类型必须是可移动赋值的。从理论上讲,这不是必需的:如果仅在末尾插入,则插入到还没有对象的位置。因此,您调用的是(移动)构造函数,而不是(移动)赋值运算符。

C++11 标准也不允许编译器/库提出该要求。因此,较新的 GCC 版本具有更智能的 push_back 实现,从而避免尝试从 push_back 编译移动赋值。

请注意,您还会遇到类模板的怪癖:类模板就像生成类的工厂。它们的成员函数体是按需生成的。也就是说,您必须在代码中的某处调用vector&lt;yourtype&gt;::push_back 才能生成此函数(“实例化”)。 vector&lt;yourtype&gt;::insert 永远不会编译,因为它尝试对 pair&lt;const key, value&gt; 进行相同的分配。不过只要不打vector&lt;yourtype&gt;::insert,没关系。

因此,对于不可赋值的类型,您仍然可以使用vector&lt;yourtype&gt;。但是你只能调用那些不使用赋值的成员函数。在 GCC 4.4 中,push_back 编译一个赋值,即使它从未被使用过。在更新的版本中,push_back 不直接或间接使用赋值。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-09
    • 2015-10-26
    • 1970-01-01
    • 2015-07-30
    • 1970-01-01
    相关资源
    最近更新 更多