GCC4.7.0库,的简单讲解和C++11带来的变化

    实习满2个月了.谨以此文缅怀本来应该是我最后一个暑假却用来实习的暑假.T_T(楼主强烈声明,实习生活很nice,前面只是吐槽,请无视)

    入正题,循惯例,上图先:

 

     简单来看,vector是继承_Vector_base,这里可以说分为两层去实现,在_Vector_base当中封装了跟内存分配相关的操作,如_M_allocate(),_M_deallocate()等,在_Vector_base当中有一个内置的结构,叫做_Vector_impl,他包含了3个指针,分别指向内存区域的起始,结束和当前使用到.这个就是vector的实现,所有的操作都是围绕在这3个指针来展开的,简单清晰._M_allocate(),_M_deallocate()其实就是对allocator申请(allocator())和释放(deallocator())接口的封装.

    看侯捷兄的源码剖析的时候,最开始讲的就是allocator,SGI的STL使用的allocator是用了内存池的技术,在分配的效率上可能会高一些.在GCC的库实现当中,默认使用的allocator是最最简单的new allocator,不使用带内存池的allocator原因可以在这里找到http://gcc.gnu.org/onlinedocs/libstdc++/manual/bk01pt04ch11.html ,GCC库当中其实提供了相当多的allocator,有机会再深入学习一下


 


 


 



    pointer的定义如下

      typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
        rebind<_Tp>::other _Tp_alloc_type;
      typedef typename __gnu_cxx::__alloc_traits<_Tp_alloc_type>::pointer
       	pointer;

    由于vector不是侵入式的容器,所以rebind<_Tp>其实是无关紧要,侵入式的容器主要是讲申请的内存结构跟T的关系,举例来说,vector申请的内存就是T的数组,这个是没有变化的,但是对于像list这样的容器,我们申请内存的时候除了主要类型T之外,我们还需要额外的两个指针,分别指向前导结点的后续结点,这样的行为可以想象成我们需要的内存被侵入,加了两个指针(总体就是T加上额外2个指针),但是我们传入模板参数的时候使用的是T,与2个额外的指针并没有什么关系.这里就需要rebind这个东西,把我们需要申请的类型重新进行绑定.加入额外的信息.__alloc_traits<>::pointer根据allocator萃取出相应的指针类型.

    与C++11相关的一些改动主要是:

    加入move构造函数,针对右值引用的构造优化,如

#ifdef __GXX_EXPERIMENTAL_CXX0X__
	_Vector_impl(_Tp_alloc_type&& __a)
	: _Tp_alloc_type(std::move(__a)),
	  _M_start(0), _M_finish(0), _M_end_of_storage(0)
	{ }
#endif
#ifdef __GXX_EXPERIMENTAL_CXX0X__
      _Vector_base(_Tp_alloc_type&& __a)
      : _M_impl(std::move(__a)) { }

      _Vector_base(_Vector_base&& __x)
      : _M_impl(std::move(__x._M_get_Tp_allocator()))
      { this->_M_impl._M_swap_data(__x._M_impl); }

      _Vector_base(_Vector_base&& __x, const allocator_type& __a)
      : _M_impl(__a)
      {
	if (__x.get_allocator() == __a)
	  this->_M_impl._M_swap_data(__x._M_impl);
	else
	  {
	    size_t __n = __x._M_impl._M_finish - __x._M_impl._M_start;
	    _M_create_storage(__n);
	  }
      }
#endif

    _M_swap_data()内部实现是利用std::swap交换_Vector_impl的3个指针的值完成move构造

#ifdef __GXX_EXPERIMENTAL_CXX0X__
      /**
       *  @brief  Creates a %vector with default constructed elements.
       *  @param  __n  The number of elements to initially create.
       *
       *  This constructor fills the %vector with @a __n default
       *  constructed elements.
       */
      explicit
      vector(size_type __n)
      : _Base(__n)
      { _M_default_initialize(__n); }

      /**
       *  @brief  Creates a %vector with copies of an exemplar element.
       *  @param  __n  The number of elements to initially create.
       *  @param  __value  An element to copy.
       *  @param  __a  An allocator.
       *
       *  This constructor fills the %vector with @a __n copies of @a __value.
       */
      vector(size_type __n, const value_type& __value,
	     const allocator_type& __a = allocator_type())
      : _Base(__n, __a)
      { _M_fill_initialize(__n, __value); }
#else
      /**
       *  @brief  Creates a %vector with copies of an exemplar element.
       *  @param  __n  The number of elements to initially create.
       *  @param  __value  An element to copy.
       *  @param  __a  An allocator.
       *
       *  This constructor fills the %vector with @a __n copies of @a __value.
       */
      explicit
      vector(size_type __n, const value_type& __value = value_type(),
	     const allocator_type& __a = allocator_type())
      : _Base(__n, __a)
      { _M_fill_initialize(__n, __value); }
#endif

    旧的vector(size_type n)的构造函数是利用默认参数进行的,如果没有提供构造的值则使用默认的value_type()来进行构造,而在C++11中将这两者进行了区分(声明了两个函数),内部实现分别调用了_M_default_initialize()和_M_fill_initialize(),应该是处于效率优化而将这两种情况进行分开的.

    不仅在构造函数中加入了对右值引用的支持,同样的还有赋值操作符重载里面.C++11带来了初始化列表,同样也在构造函数和赋值重载中有体现,如(没有全部列出)

      /**
       *  @brief  Builds a %vector from an initializer list.
       *  @param  __l  An initializer_list.
       *  @param  __a  An allocator.
       *
       *  Create a %vector consisting of copies of the elements in the
       *  initializer_list @a __l.
       *
       *  This will call the element type's copy constructor N times
       *  (where N is @a __l.size()) and do no memory reallocation.
       */
      vector(initializer_list __l,
	     const allocator_type& __a = allocator_type())
      : _Base(__a)
      {
	_M_range_initialize(__l.begin(), __l.end(),
			    random_access_iterator_tag());
      }

      /**
       *  @brief  %Vector list assignment operator.
       *  @param  __l  An initializer_list.
       *
       *  This function fills a %vector with copies of the elements in the
       *  initializer list @a __l.
       *
       *  Note that the assignment completely changes the %vector and
       *  that the resulting %vector's size is the same as the number
       *  of elements assigned.  Old data may be lost.
       */
      vector&
      operator=(initializer_list __l)
      {
	this->assign(__l.begin(), __l.end());
	return *this;
      }

    在迭代器方面,C++11提供了几个返回const迭代器的接口,包括cbegin(), cend(), crbegin(), crend().同时提供了一个data()接口用于获取内存区域的首地址,之前如果要获得vector内存区域的首地址,做法应该是&(*begin()),因为没有办法保证vector迭代器就是一个指针,所以需要先解引用之后再去地址,而data()接口封装了相关的操作

      // _GLIBCXX_RESOLVE_LIB_DEFECTS
      // DR 464. Suggestion for new member functions in standard containers.
      // data access
      /**
       *   Returns a pointer such that [data(), data() + size()) is a valid
       *   range.  For a non-empty %vector, data() == &front().
       */
#ifdef __GXX_EXPERIMENTAL_CXX0X__
      _Tp*
#else
      pointer
#endif
      data() _GLIBCXX_NOEXCEPT
      { return std::__addressof(front()); }

#ifdef __GXX_EXPERIMENTAL_CXX0X__
      const _Tp*
#else
      const_pointer
#endif
      data() const _GLIBCXX_NOEXCEPT
      { return std::__addressof(front()); }

    C++11提供了一个shrink_to_fit接口用于削减冗余的内存空间.之前则需要用类似vector(v).swap(v)这样的方法来释放那些没有用到的内存,reserve()当传入值小于当前的capacity()是不会有任何效果的.

#ifdef __GXX_EXPERIMENTAL_CXX0X__
      /**  A non-binding request to reduce capacity() to size().  */
      void
      shrink_to_fit()
      { _M_shrink_to_fit(); }
#endif

    C++11还有两个新的接口emplace_back()和emplace();

#ifdef __GXX_EXPERIMENTAL_CXX0X__
      void
      push_back(value_type&& __x)
      { emplace_back(std::move(__x)); }

      template
        void
        emplace_back(_Args&&... __args);
#endif

#ifdef __GXX_EXPERIMENTAL_CXX0X__
      /**
       *  @brief  Inserts an object in %vector before specified iterator.
       *  @param  __position  An iterator into the %vector.
       *  @param  __args  Arguments.
       *  @return  An iterator that points to the inserted data.
       *
       *  This function will insert an object of type T constructed
       *  with T(std::forward(args)...) before the specified location.
       *  Note that this kind of operation could be expensive for a %vector
       *  and if it is frequently used the user should consider using
       *  std::list.
       */
      template
        iterator
        emplace(iterator __position, _Args&&... __args);
#endif

    我觉得emplace应该是一个move版本的insert,而emplace_back是move版本的push_back,具体两者的实现在vector.tcc文件当中,在stl_vector.h里面只是一个前向声明而已.这两个接口的声明里面都使用到了C++11的新特性,可变模板(template variable),类似printf(...)的可变参数,但是这个是在模板当中的运用,编译器提供了这种核心特性的支持,处理的方式其实说白了就是递归的逐个处理,最后需要有一个模板参数为空的模板函数来作为结束递归的匹配(printf使用的是va_list相关的一些东西,我们可以用可变模板来实现一个类型安全的printf,感兴趣的同学可以找一找这份资料Andrei Alexandrescu的这份PPT,如果你搜索能力强一点,其实是还有相关的一份演讲视频的,在MSDN的channel9里面,关键字够多了吧^_^).具体的实现实际上是通过让我们传入相应的构造函数参数来构造一个临时对象,由于是临时对象,所以通过move的方式压到vector当中,这样效率就提高了(想象一个复杂很多的对象),相对之前的构造之后再次拷贝来说.这里的一个进步是不在仅仅是只能调用默认构造函数,通过可变模板的支持加上std::forward<>(完美转发),我们可以构造出任何的对象而不单单只能是默认构造函数,很多的改动都体现在效率上.STL的固有缺点就是copy,加入了move语义之后,能move的尽量move,提高效率才是王道.代码如下

#ifdef __GXX_EXPERIMENTAL_CXX0X__
  template
    template
      void
      vector<_Tp, _Alloc>::
      emplace_back(_Args&&... __args)
      {
	if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
	  {
	    _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
				     std::forward<_Args>(__args)...);
	    ++this->_M_impl._M_finish;
	  }
	else
	  _M_emplace_back_aux(std::forward<_Args>(__args)...);
      }
#endif

#ifdef __GXX_EXPERIMENTAL_CXX0X__
  template
    template
      void
      vector<_Tp, _Alloc>::
      _M_emplace_back_aux(_Args&&... __args)
      {
	const size_type __len =
	  _M_check_len(size_type(1), "vector::_M_emplace_back_aux");
	pointer __new_start(this->_M_allocate(__len));
	pointer __new_finish(__new_start);
	__try
	  {
	    _Alloc_traits::construct(this->_M_impl, __new_start + size(),
				     std::forward<_Args>(__args)...);
	    __new_finish = 0;

	    __new_finish
	      = std::__uninitialized_move_if_noexcept_a
	      (this->_M_impl._M_start, this->_M_impl._M_finish,
	       __new_start, _M_get_Tp_allocator());

	    ++__new_finish;
	  }
	__catch(...)
	  {
	    if (!__new_finish)
	      _Alloc_traits::destroy(this->_M_impl, __new_start + size());
	    else
	      std::_Destroy(__new_start, __new_finish, _M_get_Tp_allocator());
	    _M_deallocate(__new_start, __len);
	    __throw_exception_again;
	  }
	std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish,
		      _M_get_Tp_allocator());
	_M_deallocate(this->_M_impl._M_start,
		      this->_M_impl._M_end_of_storage
		      - this->_M_impl._M_start);
	this->_M_impl._M_start = __new_start;
	this->_M_impl._M_finish = __new_finish;
	this->_M_impl._M_end_of_storage = __new_start + __len;
      }
#endif
#ifdef __GXX_EXPERIMENTAL_CXX0X__
  template
    template
      typename vector<_Tp, _Alloc>::iterator
      vector<_Tp, _Alloc>::
      emplace(iterator __position, _Args&&... __args)
      {
	const size_type __n = __position - begin();
	if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage
	    && __position == end())
	  {
	    _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
				     std::forward<_Args>(__args)...);
	    ++this->_M_impl._M_finish;
	  }
	else
	  _M_insert_aux(__position, std::forward<_Args>(__args)...);
	return iterator(this->_M_impl._M_start + __n);
      }

  template
    template
      void
      vector<_Tp, _Alloc>::
      _M_insert_aux(iterator __position, _Args&&... __args)

    剩下的其他一些东西应该跟侯捷锅的STL源码剖析差异不大.可能有一些细节上的变动,但是整体的思路是没问题的,侯捷锅的书对源代码做了相应的修改,便于阅读一些,直接读GCC库实现确实还是有点小蛋疼.不过,看多一点就习惯啦.一些命名也能找到相应的规律,比如_M其实的是成员函数,而_S是静态函数.一般带_都是内部用,不是暴露出来的接口等.最后还有一点要说明的是,我这里并没有吧所有的C++11版本的源代码变动都罗列出来,没有贴出来的基本都是构造函数,复制构造函数和赋值重载对move语义支持的那些.一些细节的函数也没有列出来.童鞋们如果要深入学习可以自己看看源代码,在bits目录下的stl_vector.h,vector.tcc,默认allocator是在ext目录下的*_allocator.h文件,上面有图的.

    对于array,只是C++11加入的静态数组的支持,提供了一些比较方便的接口,分配内存是从栈里面去分配,跟T a[N]的效果是一样的.比较有趣的地方是,array支持传入参数长度为0构造一个数组,在实现当中对这种情况进行区分,如下

      // Support for zero-sized arrays mandatory.
      value_type _M_instance[_Nm ? _Nm : 1];
      reference 
      back()
      { return _Nm ? *(end() - 1) : *end(); }

    在array当中加入了对tuple相关接口的支持,具体是通过模板偏特化实现的

//1).tuple_size::value
  template
    struct tuple_size>
    : public integral_constant { };

//2).tuple_element::type
  template
    struct tuple_element<_Int, array<_Tp, _Nm> >
    { typedef _Tp type; };

//3).get(array)
  template
    constexpr _Tp&
    get(array<_Tp, _Nm>& __arr) noexcept
    { return __arr._M_instance[_Int]; }

  template
    constexpr _Tp&&
    get(array<_Tp, _Nm>&& __arr) noexcept
    { return std::move(get<_Int>(__arr)); }

    array同样提供了data()的接口可以返回内存的首地址.由于是静态数组,所以array没有提供太多复杂的接口.只有迭代器相关的接口和size(), data()等,另外重载了相关的比较操作符,方便使用.

    末尾的吐槽:昨晚看豪斯医生,看到Cameron和Chase结婚,突然有很想找个妹纸的冲动-____-,好吧,也许是因为最近跟阿肥住在一起的缘故吧(擦,两个死宅死胖纸)

    ps:昨晚看完1点半的时候,发现阿肥在打3C,跟电脑打(3C不像DOTA那样有AI,但是他还是在打....)..突然觉得有一股蛋蛋的忧伤.....

你可能感兴趣的:(Linux,各种基础)