I have been practicing for 2 months. in this article, I would like to remember the summer vacation I used for my internship. t_t)
Start:
Simply put, vector inherits _ vector_base, which can be implemented in two layers. In _ vector_base, operations related to memory allocation are encapsulated, such as _ m_allocate () and _ m_deallocate () in _ vector_base, there is a built-in structure called _ vector_impl. It contains three pointers pointing to the start, end, and current usage of the memory area respectively. this is the implementation of vector. All operations are carried out around the three pointers, which are simple and clear. _ m_allocate (), _ m_deallocate () is actually encapsulation of the Allocator application (Allocator () and release (deallocator () interfaces.
When looking at Hou Jie's source code analysis, he first talked about allocator. the Allocator used by the sgi stl uses the memory pool technology, which may increase the allocation efficiency. in the GCC library implementation, the Allocator used by default is the simplest new Allocator, the reason for not using Allocator with memory pool can be found here http://gcc.gnu.org/onlinedocs/libstdc++/manual/bk01pt04ch11.html
In fact, many Allocator instances are provided in the GCC library, so you have the opportunity to learn more.
Pointer is defined as follows:
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;
Because vector is not an intrusive container, rebind <_ TP> is irrelevant. The intrusive container mainly talks about the relationship between the applied memory structure and T. For example, vector <t> the applied memory is the T array, which remains unchanged. However, for containers like list <t>, when applying for memory, except for the main type T, we also need two additional pointers pointing to the subsequent nodes of the leading node, which can be imagined as memory intrusion, two pointers are added (T and 2 extra pointers), but t is used when we pass in the template parameters. It has nothing to do with the two additional pointers. here we need rebind to rebind the type we need to apply. add additional information. _ alloc_traits <>: pointer extracts pointer types based on allocator.
Some major changes related to c ++ 11 are:
Add the move constructor to optimize the construction of the right value reference, as shown in figure
#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
The internal implementation of _ m_swap_data () is to use the values of the three pointers of STD: swap exchange _ vector_impl to complete the move construction.
#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
The constructor of the old vector (size_type N) uses the default parameter. If the constructor does not provide the constructed value, it uses the default value_type () for construction, in C ++ 11, the two functions are distinguished (two functions are declared). The internal implementation calls _ m_default_initialize () and _ m_fill_initialize () respectively (), they should be separated by efficiency optimization.
Not only does the constructor support right value reference, but it also contains the value assignment operator overload. c ++ 11 brings the initialization list, which is also reflected in constructor and value assignment overload, for example (not all listed)
/** * @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<value_type> __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<value_type> __l) {this->assign(__l.begin(), __l.end());return *this; }
In terms of the iterator, C ++ 11 provides several interfaces for returning the const iterator, including cbegin (), Cend (), crbegin (), and crend (). at the same time, a data () interface is provided to obtain the first address of the memory area. If you want to obtain the first address of the vector memory area before, it should be & (* begin ()), because there is no way to ensure that the vector iterator is a pointer, We Need To unreference it first and then go to the address. The data () interface encapsulates related operations.
// _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 provides a shrink_to_fit interface to reduce redundant memory space. previously, we needed to use a vector like <t> (V ). swap (v) is used to release unused memory. Reserve () does not have any effect when the input value is smaller than the current 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 has two new interfaces, emplace_back () and emplace ();
#ifdef __GXX_EXPERIMENTAL_CXX0X__ void push_back(value_type&& __x) { emplace_back(std::move(__x)); } template<typename... _Args> 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>(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<typename... _Args> iterator emplace(iterator __position, _Args&&... __args);#endif
I think emplace should be a move version of insert, and emplace_back is the move version of push_back. in the TCC file, stl_vector.h is just a Forward Declaration. the declaration of these two interfaces uses the new features of C ++ 11, the variable template (template variable), similar to printf (...) variable parameters, but this is used in the template. The Compiler provides support for this core feature. The processing method is actually to put it bluntly, recursive processing is performed one by one, finally, a template function with an empty template parameter is required to end recursive matching. (printf uses va_list-related items, we can use a variable template to implement a type-safe printf. If you are interested, you can find this document Andrei.
Alexandrescu's <variadic templates> PPT, if you have a strong search capability, there is actually a related speech video, In the channel9 of msdn, enough keywords, right? ^_^ ). the specific implementation is actually to construct a temporary object by passing in the corresponding constructor parameters. Because it is a temporary object, we press it to the vector by moving, in this way, the efficiency is improved (imagine a complicated object), compared to copying it again after the previous structure. the progress here is not only to call the default constructor, but to add STD: Forward <> (perfect forwarding) to the support of variable templates ), we can construct any object, not just the default constructor. Many changes are reflected in efficiency. the inherent disadvantage of STL is copy. After the move semantics is added, try to move as much as possible to improve efficiency. the Code is as follows:
#ifdef __GXX_EXPERIMENTAL_CXX0X__ template<typename _Tp, typename _Alloc> template<typename... _Args> 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<typename _Tp, typename _Alloc> template<typename... _Args> 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<typename _Tp, typename _Alloc> template<typename... _Args> 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<typename _Tp, typename _Alloc> template<typename... _Args> void vector<_Tp, _Alloc>:: _M_insert_aux(iterator __position, _Args&&... __args)
The rest of the other things should be slightly different from the STL source code analysis of Hou jiepan. there may be some changes in details, but the overall idea is no problem. The Book of Hou jiepot has modified the source code to facilitate reading, reading the GCC library directly is really a little tricky. however, if you look a little more, you will get used to it. some naming rules can also be found. For example, _ m is actually a member function, and _ S is a static function. generally, _ is used internally, rather than exposed interfaces. finally, it should be noted that I have not listed all the source code changes in C ++ 11, and basically all the unposted constructors, copy constructors and assign values to those that are supported by the move semantics. some detailed functions are not listed. if you want to learn more, take a look at the source code, in the bits directory, stl_vector.h, vector. TCC. By default, allocator is * _ Allocator in the ext directory. h file, as shown in the figure above.
Array is only supported by static arrays added to C ++ 11. It provides some convenient interfaces for allocating memory from the stack, the effect is the same as that of t a [n. the interesting thing is that array supports the input parameter length of 0 to construct an array. in implementation, this situation is distinguished as follows:
// Support for zero-sized arrays mandatory. value_type _M_instance[_Nm ? _Nm : 1];
reference back() { return _Nm ? *(end() - 1) : *end(); }
Added support for tuple-related interfaces to the array, specifically implemented through partial template features.
//1).tuple_size<array>::value template<typename _Tp, std::size_t _Nm> struct tuple_size<array<_Tp, _Nm>> : public integral_constant<std::size_t, _Nm> { };//2).tuple_element<array>::type template<std::size_t _Int, typename _Tp, std::size_t _Nm> struct tuple_element<_Int, array<_Tp, _Nm> > { typedef _Tp type; };//3).get<N>(array) template<std::size_t _Int, typename _Tp, std::size_t _Nm> constexpr _Tp& get(array<_Tp, _Nm>& __arr) noexcept { return __arr._M_instance[_Int]; } template<std::size_t _Int, typename _Tp, std::size_t _Nm> constexpr _Tp&& get(array<_Tp, _Nm>&& __arr) noexcept { return std::move(get<_Int>(__arr)); }
Array also provides the data () interface to return the first address of the memory. because it is a static array, array does not provide too many complex interfaces. only the iterator-related interfaces, such as size () and data (), and the related comparison operators are reloaded for ease of use.
At the end of the discussion: I saw Dr. Haos last night and saw Cameron and Chase get married. Suddenly I wanted to find a sister paper.-____-, okay, maybe it's because I recently lived with a fat app)
PS: At half past one last night, I found a fat player playing 3C and playing with a computer (3C is not as AI as Dota, but he is still playing ....).. suddenly I felt an egg of sorrow .....