[Reprint] Thoroughly learn the allocator in STL

Source: Internet
Author: User

Original: http://cissco.iteye.com/blog/379093

To help us understand the principles of allocator.

Allocator is one of the most mysterious parts of the C + + language standard library. They are rarely used explicitly, and the standard does not specify when they should be used. Today's allocator is very different from the original STL advice, and there are two other designs in the process-both of which depend on some of the language's features, and are only recently available on a few compilers. For allocator's function, standards seem to have added promise in some respects, while others have withdrawn their commitment.

This column will discuss what you can do with allocator and how to define a version of yourself. I will only discuss the allocator defined by the C + + standard: Introducing the design of a quasi-standard era, or trying to bypass a flawed compiler, will only add to the clutter.

When not to use allocator

The allocator in the C + + standard is divided into two blocks: a common requirements set (described in §20.1.5 (Table 32)), and the class called Std::allocator (described in §20.4.1). If a class satisfies the requirements of table 32, we call it a allocator. The Std::allocator class satisfies those needs, so it is a allocator. It is the only pre-defined allocator class in the standard library.

Each C + + programmer already knows the dynamic memory allocation: Write down new x to allocate memory and create a new object of type X, write down delete p to destroy the object referred to by P and return its memory. You have reason to think that allocator will use new and delete--but they do not. (the C + + standard will: operator new () is described as "allocation function", but strangely, allocator is not. )

The most important fact about allocator is that they are just for one purpose: to encapsulate the low-level details of the STL container in memory management. You should not call allocator's member function directly in your own code unless you are writing an STL container of your own. You should not try to use allocator to implement operator new[]; This is not what allocator should do. If you're not sure if you need to use allocator, don't use it.

Allocator is a class that has member functions called allocate () and deallocate () (equivalent to malloc and free). It also has auxiliary functions for maintaining the allocated memory and a typedef (the name of a pointer or reference type) that indicates how the memory is used. If an STL container uses a user-supplied allocator to allocate all of the memory it needs (predefined STL containers can do all of this; they all have a template parameter whose default value is Std::allocator). You can control its memory management by providing your own allocator.

This flexibility is limited: it is still up to the container to decide how much memory it will apply and how to use them. When the container requests more memory, you can control it to call that low-level function, but you can't use allocator to make a vector act like a deque. Even so, sometimes, this limited flexibility is also useful. For example, suppose you have a special fast_allocator that can quickly allocate and release memory (perhaps by discarding thread safety, or using a small local heap), you can write down std::list<t, fast_allocator<t> > Rather than a simple std::list<t> to let the standard list use it.

If this looks strange to you, that's right. There is no reason to use allocator in regular code.

Define a allocator

You've seen this about allocator: they're templates. Allocator, like a container, has value_type, and Allocator's value_type must match the value_type of the container that uses it. This is sometimes ugly: map's value_type is quite complex, so explicitly calling allocator's map looks like this, Std::map<k,v, fast_allocator<std::p air<const K, V > > >. (as usual, typedef can help.) )

Start with a simple example. According to the C + + standard, Std::allocator is built on:: operator new (). If you're using a tracking tool with an automated memory, it's much easier to make std::allocator easier. We can replace with malloc (): operator new (), and we do not consider complex performance optimizations (which can be found in good std::allocator practices). We called this simple allocator the malloc_allocator.

Since Malloc_allocator's memory management is simple, we can focus on the templates common to all STL allocator. First, some types: allocator is a class template whose instances are designed to allocate memory for a type T. We provide a sequence of typedef to describe how this type of object is used: Value_type refers to t itself, and the others are pointers and references with various modifier words.

CPP Code
  1. Template <class t> class Malloc_allocator
  2. {
  3. Public
  4. typedef T VALUE_TYPE;
  5. typedef value_type* Pointer;
  6. typedef const VALUE_TYPE* Const_pointer;
  7. typedef value_type& Reference;
  8. typedef const value_type& Const_reference;
  9. typedef std::size_t SIZE_TYPE;
  10. typedef std::p trdiff_t difference_type;
  11. ...
  12. };

These types are similar to those in STL containers, and this is no coincidence: container classes often extract these types directly from their allocator.

Why are there so many typedef? You may think pointer is superfluous: it is value_type *. Most of the time this is right, but you may sometimes want to define non-traditional allocator, its pointer is a Pointer-like class, or non-standard vendor-specific type Value_type __far * Allocator is a standard hook for non-standard expansion. The unusual pointer type is also the reason for the presence of the address () member function, which in Malloc_allocator is just another way of Operator & ():

CPP Code
    1. template <class t> class malloc_allocator   
    2.   
    3.     {  
    4.   
    5.  & nbsp;  public:  
    6.   
    7.       pointer  address (reference x)  const { return &x; }  
    8.    
    9.       const_pointer address (const_reference x)   const {   
    10.   
    11.          return &x;   
    12.   
    13.       }   
    14.   
    15.       ...  
    16.   
    17.     };  

Now we can start the real job: Allocate () and deallocate (). They are simple, but not very much like malloc () and free (). We passed to allocate () two parameters: the number of objects we are allocating space for (Max_size returns the maximum requested value that might succeed), and an optional address value (which can be used as a location hint). A simple allocator like malloc_allocator does not take advantage of that hint, but a allocator designed for high performance might take advantage of it. The return value is a pointer to a memory block that is large enough to hold an object of n value_type type and has the correct alignment.

We also passed to deallocate () two parameters: Of course one is a pointer, but there is also an element count value. The container must master the size information itself; The size parameters passed to allocate and deallocate must match. Again, this extra parameter is available for efficiency, and again, Malloc_allocator does not use it.

CPP Code
  1. Template <class t> class Malloc_allocator
  2. {
  3. Public
  4. Pointer allocate (size_type n, const_pointer = 0) {
  5. void* p = std::malloc (n * sizeof (T));
  6. if (!p)
  7. Throw Std::bad_alloc ();
  8. Return static_cast<pointer> (P);
  9. }
  10. void deallocate (pointer p, size_type) {
  11. Std::free (P);
  12. }
  13. Size_type max_size () const {
  14. Return static_cast<size_type> ( -1)/sizeof (VALUE_TYPE);
  15. }
  16. ...
  17. };

The allocate () and deallocate () member functions deal with uninitialized memory, which does not construct and destroy objects. The statement a.allocate (1) is more like malloc (sizeof (int)) than the new int. Before using the memory obtained from allocate (), you must create objects on this block of memory, and before returning the memory through Deallocate (), you need to destroy those objects.

The C + + language provides a mechanism to create objects in a specific memory location: placement new. If you write new (P) T (A, B), then you are calling the constructor of T to produce a novel object, as you wrote in New T (A, B) or T T (A, B). The difference is when you write new (p) T (A, B), you specify where the object is created: P points to the address. (Naturally, p must point to a chunk of memory that is large enough and must be unused memory; You cannot build two different objects at the same address.) )。 You can also call the object's destructor without releasing the memory, just write P->~t ().

These features are rarely used because the allocation and initialization of memory is usually done together: it is inconvenient and dangerous to use uninitialized memory. One of the few places you need such a low level technique is that you're writing a container class, so allocator the allocation of memory to the initial decoupling. The member function construct () calls placement new, and the member function destory () calls the destructor.

CPP Code
    1. template <class t> class malloc_allocator   
    2.   
    3.     {  
    4.   
    5.  & nbsp;  public:  
    6.   
    7.       void  construct (pointer p, const value_type& x)  {   
    8.   
    9.         new (P)  value_type (x);    
    10.   
    11.       }  
    12.   
    13.       void destroy (pointer p)  { p->~value_type () ;  }  
    14.   
    15.       ...  
    16.   
    17.     };  

(Why allocator have those member functions, when can the container use placement new directly?) One reason is to hide the clumsy syntax, and the other is if you want to write a more complex allocator when you're trying to construct and destroy objects construct () and destroy () have some other side effects. For example, allocator may maintain a log of all currently active objects. )

None of these member functions is static, so the first thing a container does before using allocator is to create a allocator object-that is, we should define some constructors. However, we do not need an assignment: Once the container has created its allocator, the allocator never thought it would be changed. The requirements for allocator in table 32 do not include assignments. Just based on security, in order to make sure that no one accidentally uses the assignment, we will disallow this function that might be automatically generated.

CPP Code
  1. Template <class t> class Malloc_allocator
  2. {
  3. Public
  4. Malloc_allocator () {}
  5. Malloc_allocator (const malloc_allocator&) {}
  6. ~malloc_allocator () {}
  7. Private
  8. void operator= (const malloc_allocator&);
  9. ...
  10. };

These constructors do not actually do anything, because this allocator does not need to initialize any member variables. For the same reason, any two malloc_allocator are interchangeable, and if the types of A1 and A2 are Malloc_allocator<int>, we are free to A1 through allocate () The memory then deallocate () it through A2. We therefore define a comparison operation to indicate that all Malloc_allocator objects are equivalent:

CPP Code
  1. Template <class t>
  2. inline bool operator== (const MALLOC_ALLOCATOR<T>&
  3. Const malloc_allocator<t>&) {
  4. return true;
  5. }
  6. Template <class t>
  7. inline bool operator!= (const MALLOC_ALLOCATOR<T>&
  8. Const malloc_allocator<t>&) {
  9. return false;
  10. }

Would you expect a allocator, its different objects are not replaceable? Of course--but it's hard to provide a simple and useful example. One obvious possibility is the memory pool. It is common for large C programs to allocate memory from several different locations ("pools") instead of everything directly using malloc (). This has several benefits, one of which is it only takes a single function call to reclaim all of the memory associated with a particular phase of the PR Ogram. Programs that use a memory pool may define tool functions such as Mempool_alloc and Mempool_free, and Mempol_alloc (n, p) allocates n bytes from the pool p. It is easy to write a mmepool_alocator to match such a schema: Each Mempool_allocator object has a member variable to indicate which pool it is bound to, and mempool_allocator::allocate () Call Mempool_alloc () to get memory from the appropriate pool. [Note 1]

Finally, we go to a subtle part of the definition of allocator: mapping between different types. The problem is that a allocator class, such as Malloc_allocator<int>, is all built around a single value_type: malloc_allocator<int>::p Ointer is int* ,malloc_allocator<int> (). Allocate (1) returns enough memory to hold an int object, and so on. However, typically, a container class using malloc_allocator may have to handle more than one type. For example, a list class, which does not allocate an int object, actually assigns a list node object. (We'll look at the details in the next paragraph.) So, when you create a std::list<int, malloc_allocator<int> >, the list must turn malloc_allocator<int> into a process list_ Node type of malloc_allocator.

This mechanism is called re-binding, and it has two parts. First, for a given value_type is the X1 allocator type A1, you must be able to write a allocator type A2, which is exactly the same as A1, except Value_type is X2. Second, for a given A1 type of object A1, you must be able to create an equivalent A2 type Object A2. Both sections use member templates, which is why allocator cannot be supported by older compilers or poorly supported.

[Reprint] Thoroughly learn the allocator in STL

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.