We also talk about allocator of stl.

Source: Internet
Author: User

A few days ago, I saw a discussion post on map on the forum. This article mainly discusses how to avoid using the underlying memory pool mechanism of map! Some people say that the map distributor (Allocator) can be customized to achieve the goal. Is that true?

Let's talk about Allocator ....

--------------------

Article 10 of effectivestl says: "Most standard containers never ask for memory from their related distributors ". Note that they are related here!

The reason is:

Clause 10: Pay attention to the agreement and constraints of the distributor ---stlchina.org

"When we add a new node to the list, we need to obtain the memory from the distributor. What we want is not the memory of T, but the memory of a listnode containing T. That makes our Allocator object useless, because it does not allocate memory for listnode, it allocates memory for T. Now you understand why a list has never been allocated to its Allocator: the distributor cannot provide the list ."

The book also mentions a website:

Http://www.josuttis.com/cppcode/myalloc.hpp.html

We can see that there is a simple implementation of allocator. But there are some details that are easy to mislead people, and there is something wrong with it.

Write Your Own Allocator. Pay attention to the following points:

1. typedef:

Typedef T * pointer; <br/> typedef const T * const_pointer; <br/> typedef T & reference; <br/> typedef const T & const_reference;

The above four typedef are used in the underlying STL containers. Without these definitions, compilation errors may occur. The list contains the source code:

63 typedef typename _ Allocator: pointer; <br/> 64 typedef typename _ Allocator: const_pointer;

2. rebind: Other Definition:

Template <typename U> <br/> struct rebind <br/>{< br/> typedef MyAlloc <U> other; <br/>}; <br/>
To put it simply, in fact, the memory allocation in containers such as list is through this other. That is to say, the memory allocation for the list <t, Allocator <t> is not allocated by Allocator <t>, but by Allocator <listnode <t>, now I understand "they are related" in the sentence "most standard containers never ask for memory from their related distributors? Therefore, it must be defined as a template. In the source code of list, there are:

Typedef typename _ alloc: Template rebind <_ list_node <_ TP> >:: other _ node_alloc_type;
If you do not define rebind, an error occurs when compiling list-related code... Of course, for vector, it may not!

3. Definitions of allocate and deallocate functions:

Pointer allocate (size_t num) <br/>{< br/> cout <"allocate:" <num <Endl; <br/> return (pointer ):: operator new (Num * sizeof (t); <br/>}< br/> void deallocate (pointer P, size_t num) <br/>{< br/> cout <"deallocate:" <typeid (P ). name () <"" <num <Endl; <br/>: Operator Delete (void *) P); <br/>}

Note that pointer ret = (pointer) (: Operator new (Num * sizeof (t) is used here; that is, only the memory is allocated and not initialized, the actual initialization of the new operation (called by the constructor) is in a function called STD: _ construct rather than in the construct function on the URL above. The containers in STL call Allocator and then call the STD: _ construct function!
309 _ List_node <_ Tp> * <br/> 310 _ M_get_node () <br/> 311 {return _ M_impl. _ Node_Alloc_type: allocate (1) ;}< br/> 312 <br/> ...... <Br/> 432 _ Node * <br/> 433 _ M_create_node (const value_type & _ x) <br/> 434 {<br/> 435 _ Node * _ p = this-> _ M_get_node (); <br/> 436 try <br/> 437 {<br/> 438 std: _ Construct (& __ p-> _ M_data, _ x ); <br/> 439} <br/> 440 catch (...) <br/> 441 {<br/> 442 _ M_put_node (_ p); <br/> 443 _ throw_exception_again; <br/> 444} <br/> 445 return _ p; <br/> 446}

 

Note: It may be because my GCC version is old. in GCC 4.4.3, the source code has been modified:

457 # ifndef _ GXX_EXPERIMENTAL_CXX0X __< br/> 458 _ Node * <br/> 459 _ M_create_node (const value_type & _ x) <br/> 460 {<br/> 461 _ Node * _ p = this-> _ M_get_node (); <br/> 462 _ try <br/> 463 {<br/> 464 _ M_get_Tp_allocator (). construct (& __ p-> _ M_data, _ x); <br/> 465} <br/> 466 _ catch (...) <br/> 467 {<br/> 468 _ M_put_node (_ p); <br/> 469 _ throw_exception_again; <br/> 470} <br/> 471 return _ p; <br/> 472} <br/> 473 # else <br/> 

 

We can see that if you use this version of code, Allocator must provide the construct and destruct functions!

4. Constructor
MyAlloc () throw () {cout <"myalloc ()" <endl ;} <br/> template <typename U> <br/> MyAlloc (const MyAlloc <U> &) throw () {cout <"myalloc (const &" <typeid (U ). name () <")" <endl ;}


If you do not add these two functions, the following may occur during compilation:
'Std: List <_ TP, _ alloc >:: list (const typename STD: _ list_base <_ TP, _ alloc >:: allocator_type &) [With _ TP = ELEM, _ alloc = myalloc <ELEM>]'
This error message. The reason is simple,
Code:

295 typedef typename _ Alloc: template rebind <_ List_node <_ Tp >:: other <br/> 296 <br/> 297 _ Node_Alloc_type; <br/> ...... <Br/> 299 struct _ List_impl <br/> 300: public _ Node_Alloc_type {<br/> 301 _ List_node_base _ M_node; <br/> 302 _ List_impl (const _ Node_Alloc_type & _ a) <br/> 303: _ Node_Alloc_type (_) <br/> 304 {}< br/> 305 }; <br/> ...... <Br/> ...... <Br/> 476 explicit <br/> 477 list (const allocator_type & _ a = allocator_type () <br/> 478: _ Base (_ ){}

Row 478: The parameter type received by _ base is _ node_alloc_type (that is, myalloc <_ list_node <_ TP>, but the type of parameter _ A is myalloc <ELEM>
Therefore, the replication constructor must be defined using a template. Of course, if only the replication constructor does not have the default constructor, can you compile it?

Note: This is not required in the vector, because the node stored in the vector is the same as ELEM. ELEM is the node and there is no additional data field. And rebind: Other does not need to be... After all, the node memory distributor of the vector is the ELEM distributor.

With the above knowledge, the program is basically formed:

# Include <iostream> <br/> # include <list> <br/> # include <vector> <br/> using namespace std; <br/> class Elem <br/> {<br/> public: <br/> Elem () {cout <"Elem ()" <endl ;} <br/> Elem (const Elem &) {cout <"Elem (const Elem &)" <endl ;}< br/> ~ Elem () {cout <"~ Elem () "<endl ;}< br/> int m_ I; <br/> float m_f; <br/> }; <br/> template <typename T> <br/> class MyAlloc <br/>{< br/> public: <br/> typedef T * pointer; <br/> typedef const T * const_pointer; <br/> typedef T & reference; <br/> typedef const T & const_reference; <br/> template <typename U> <br/> struct rebind <br/>{< br/> typedef MyAlloc <U> other; <br/> }; <br/> pointer allocate (size_t num) <br/> {<br/> cout <"allocate:" <num <endl; <br/> return (pointer): operator new (num * sizeof (T); <br/>}< br/> void deallocate (pointer p, size_t num) <br/>{< br/> cout <"deallocate:" <typeid (p ). name () <"" <num <endl; <br/>: operator delete (void *) p ); <br/>}< br/> MyAlloc () throw () {cout <"myalloc ()" <endl ;} <br/> template <typename U> <br/> MyAlloc (const MyAlloc <U> &) throw () {cout <"myalloc (const &" <typeid (U ). name () <")" <endl ;}< br/>}; <br/> template <typename T1, typename T2> <br/> bool operator = (const MyAlloc <T1> &, const MyAlloc <T2> &) <br/>{< br/> return true; <br/>}< br/> int main () <br/>{< br/> // vector <Elem, MyAlloc <Elem> vec; <br/> Elem e; <br/> // vec. push_back (e); <br/> list <Elem, MyAlloc <Elem> li; <br/> li. push_back (e); <br/> // li. begin ()-> m_ I = 10; <br/> // list <Elem, MyAlloc <int> lii; <br/> // lii. insert (lii. begin (), li. begin (), li. end (); <br/> // cout <typeid (Elem ). name () <endl; <br/> // li. insert (li. begin (), vec. begin (), vec. end (); <br/> // cout <li. size () <endl; <br/> return 0; <br/>}

OK. Now we can clearly see the memory redistribution of containers such as vector !!

If you are careful, you can find the bool operator = (const myalloc <t1> &, const myalloc <t2> &) function!

There is a paragraph in Article 10 of valid STL:
"When L1 is destroyed, of course, it must destroy all its nodes (and reclaim their memory) because it now contains nodes originally part of L2, l1 distributors must recycle nodes originally allocated by L2 distributors. It is now clear why the standard allows STL implementation to think that the same type of distributor is equivalent. Therefore, the memory allocated by one distributor object (such as l2) can be safely recycled by another distributor object (such as L1. Without such an assumption, the joint operation will be more difficult to implement. Obviously they cannot be as efficient as they are now"
Therefore, such an operator = must be overloaded. Of course, during the test, no such operator overload was found and no exception was found...

------------------------

Well, after understanding the above principles, I went back to the initial issue. Can the underlying memory pool mechanism of map be changed by modifying allocator? I guess not. Map's memory pool mechanism does not call allocator's deallocate function at all. If you still do not understand, you can try to change the program ~~~

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.