天天看点

C++ STL|User-Defined memory Allocator and using

作者:小智雅汇

Allocators provide an interface to allocate, create, destroy, and deallocate objects. With allocators, containers and algorithms can be parametrized based on the way the elements are stored. For example, you could implement allocators that use shared memory or that map the elements to a persistent database.

Expression Effect
a.allocate(num) Allocates memory for num elements
a.construct(p,val) Initializes the element to which p refers with val
a.destroy(p) Destroys the element to which p refers
a.deallocate(p,num) Deallocates memory for num elements to which p refers

Writing your own allocator is not very hard. The most important issue is how you allocate or deallocate the storage. For the rest, appropriate defaults are usually provided since C++11. (Before C++11, you had to implement the rest in a pretty obvious way. ) As an example, let’s look at an allocator that behaves just like the default allocator:

#include <cstddef>    // for size_t

template <typename T>
class MyAlloc {
  public:
    // type definitions
    typedef T value_type;

    // constructors
    // - nothing to do because the allocator has no state
    MyAlloc () noexcept {
    }
    template <typename U>
    MyAlloc (const MyAlloc<U>&) noexcept {
        // no state to copy
    }

    // allocate but don't initialize num elements of type T
    T* allocate (std::size_t num) {
        // allocate memory with global new
        return static_cast<T*>(::operator new(num*sizeof(T)));
    }

    // deallocate storage p of deleted elements
    void deallocate (T* p, std::size_t num) {
        // deallocate memory with global delete
        ::operator delete(p);
    }
};

// return that all specializations of this allocator are interchangeable
template <typename T1, typename T2>
bool operator== (const MyAlloc<T1>&,
                 const MyAlloc<T2>&) noexcept {
    return true;
}
template <typename T1, typename T2>
bool operator!= (const MyAlloc<T1>&,
                 const MyAlloc<T2>&) noexcept {
    return false;
}

#include <vector>
#include <map>
#include <string>
#include <functional>

int main()
{
    // a vector with special allocator
    std::vector<int,MyAlloc<int>> v;

    // an int/float map with special allocator
    std::map<int,float,std::less<int>,
             MyAlloc<std::pair<const int,float>>> m;

    // a string with special allocator
    std::basic_string<char,std::char_traits<char>,MyAlloc<char>> s;


    // special string type that uses special allocator
    typedef std::basic_string<char,std::char_traits<char>,
                              MyAlloc<char>> MyString;

    // special string/string map type that uses special allocator
    typedef std::map<MyString,MyString,std::less<MyString>,
                     MyAlloc<std::pair<const MyString,MyString>>> MyMap;
    // create object of this type
    MyMap mymap;
    //...
}           

As the example demonstrates, you have to provide the following features:

• A type definition of the value_type, which is nothing but the passed template parameter type.

• A constructor.

A template constructor, which copies the internal state while changing the type. Note that a template constructor does not suppress the implicit declaration of the copy constructor.

• A member allocate(), which provides new memory.

• A member deallocate(), which releases memory that is no longer needed.

• Constructors and a destructor, if necessary, to initialize, copy, and clean up the internal state.

• Operators == and !=.

You don’t have to provide construct() or destroy(), because their default implementations usually work fine (using placement new to initialize the memory and calling the destructor explicitly to clean it up). Using this base implementation, you should find it easy to implement your own allocator. You can use the core functions allocate() and deallocate() to implement your own policy of memory allocation, such as reusing memory instead of freeing it immediately, using shared memory, mapping the memory to a segment of an object-oriented database, or just debugging memory allocations.

In addition, you might provide corresponding constructors and a destructor to provide and release what allocate() and deallocate() need to fulfill their task.

Note that before C++11, you had to provide a lot more members, which, however, were easy to provide.

#include <cstddef>
#include <memory>
#include <limits>

template <typename T>
class MyAlloc {
  public:
    // type definitions
    typedef std::size_t    size_type;
    typedef std::ptrdiff_t difference_type;
    typedef T*             pointer;
    typedef const T*       const_pointer;
    typedef T&             reference;
    typedef const T&       const_reference;
    typedef T              value_type;

    // constructors and destructor
    // - nothing to do because the allocator has no state
    MyAlloc() throw() {
    }
    MyAlloc(const MyAlloc&) throw() {
    }
    template <typename U>
      MyAlloc (const MyAlloc<U>&) throw() {
    }
    ~MyAlloc() throw() {
    }

    // allocate but don't initialize num elements of type T
    T* allocate (std::size_t num, const void* hint = 0) {
        // allocate memory with global new
        return static_cast<T*>(::operator new(num*sizeof(T)));
    }

    // deallocate storage p of deleted elements
    void deallocate (T* p, std::size_t num) {
        // deallocate memory with global delete
        ::operator delete(p);
    }

    // return address of values
    T* address (T& value) const {
        return &value;
    }
    const T* address (const T& value) const {
        return &value;
    }

    // return maximum number of elements that can be allocated
    std::size_t max_size () const throw() {
        return std::numeric_limits<std::size_t>::max() / sizeof(T);
    }

    // initialize elements of allocated storage p with value value
    void construct (T* p, const T& value) {
        // initialize memory with placement new
        ::new((void*)p)T(value);
    }

    // destroy elements of initialized storage p
    void destroy (T* p) {
        // destroy objects by calling their destructor
        p->~T();
    }

    // rebind allocator to type U
    template <typename U>
    struct rebind {
        typedef MyAlloc<U> other;
    };
};

// return that all specializations of this allocator are interchangeable
template <typename T1, typename T2>
bool operator== (const MyAlloc<T1>&,
                 const MyAlloc<T2>&) throw() {
    return true;
}
template <typename T1, typename T2>
bool operator!= (const MyAlloc<T1>&,
                 const MyAlloc<T2>&) throw() {
    return false;
}

#include <vector>
#include <map>
#include <string>
#include <functional>

int main()
{
    // a vector with special allocator
    std::vector<int,MyAlloc<int> > v;

    // an int/float map with special allocator
    std::map<int,float,std::less<int>,
             MyAlloc<std::pair<const int,float> > > m;

    // a string with special allocator
    std::basic_string<char,std::char_traits<char>,MyAlloc<char> > s;


    // special string type that uses special allocator
    typedef std::basic_string<char,std::char_traits<char>,
                              MyAlloc<char> > MyString;

    // special string/string map type that uses special allocator
    typedef std::map<MyString,MyString,std::less<MyString>,
                     MyAlloc<std::pair<const MyString,MyString> > > MyMap;
    // create object of this type
    MyMap mymap;
    //...
}           

ref:

Nicolai M. Josuttis 《The C++ Standard Library》

-End-

继续阅读