Open In App

Stack-buffer based STL allocator

Last Updated : 26 Oct, 2022
Improve
Improve
Like Article
Like
Save
Share
Report

It is absolutely possible to develop a stack allocator that completely complies with C++11 and C++14. However, you must take into account some of the implications of stack allocation’s implementation, semantics, and interactions with common containers.

Here is a fully compliant stack allocator for C++11 and C++14:-

Stack buffer: When information written to a memory location exceeds the size of that data, a buffer overflow occurs. This can cause program crashes, data corruption, or malicious code to run on your computer.

Approach: The main idea is to 

Initially draw memory from a user-supplied fixed-size buffer. Then this allocator will switch to a backup allocator (by default, the std::allocator<T>) when it runs out of room.

  • A straightforward approach is to use a stack pointer for allocations and deallocation of memory.
  • Increment the stack pointer while allocating and decrease it while deallocating.

This method restricts greatly how you can use the allocator. If used appropriately, it will work properly for something which allocates continuous memory blocks (like vector) but not for something that allocates and deallocates in varying order (like a map).

Allocator in C++: Allocator is a kind of object which is responsible for encapsulating memory management. All containers in the Standard Template Library (STL) have a type parameter Allocator that is defaulted to std::allocator. The default allocator uses operators new and delete to obtain and release memory, respectively.

Declaration of Template:  template <class X> class allocator

C++




#include <functional>
#include <memory>
  
// Class Declaration
template <class T, std::size_t N, class Allocator = std::allocator<T> >
class stack_allocator {
public:
    typedef typename std::allocator_traits<Allocator>::value_type value_type;
    typedef typename std::allocator_traits<Allocator>::pointer pointer;
    typedef typename std::allocator_traits<Allocator>::const_pointer const_pointer;
    typedef typename Allocator::reference reference;
    typedef typename Allocator::const_reference const_reference;
    typedef typename std::allocator_traits<Allocator>::size_type size_type;
    typedef typename std::allocator_traits<Allocator>::difference_type difference_type;
  
    typedef typename std::allocator_traits<Allocator>::const_void_pointer const_void_pointer;
    typedef Allocator allocator_type;
  
public:
    explicit stack_allocator(const allocator_type& alloc = allocator_type())
        : m_allocator(alloc), m_begin(nullptr), m_end(nullptr), m_stack_pointer(nullptr)
    {
    }
  
    explicit stack_allocator(pointer buffer, const allocator_type& alloc = allocator_type())
        : m_allocator(alloc), m_begin(buffer), m_end(buffer + N), m_stack_pointer(buffer)
    {
    }
  
    template <class U>
    stack_allocator(const stack_allocator<U, N, Allocator>& other)
        : m_allocator(other.m_allocator), m_begin(other.m_begin), m_end(other.m_end), m_stack_pointer(other.m_stack_pointer)
    {
    }
  
    constexpr static size_type capacity()
    {
        return N;
    }
  
    pointer allocate(size_type n, const_void_pointer hint = const_void_pointer())
    {
        if (n <= size_type(std::distance(m_stack_pointer, m_end))) {
            pointer result = m_stack_pointer;
            m_stack_pointer += n;
            return result;
        }
  
        return m_allocator.allocate(n, hint);
    }
  
    void deallocate(pointer p, size_type n)
    {
        if (pointer_to_internal_buffer(p)) {
            m_stack_pointer -= n;
        }
        else
            m_allocator.deallocate(p, n);
    }
  
    size_type max_size() const noexcept
    {
        return m_allocator.max_size();
    }
  
    template <class U, class... Args>
    void construct(U* p, Args&&... args)
    {
        m_allocator.construct(p, std::forward<Args>(args)...);
    }
  
    template <class U>
    void destroy(U* p)
    {
        m_allocator.destroy(p);
    }
  
    pointer address(reference x) const noexcept
    {
        if (pointer_to_internal_buffer(std::addressof(x))) {
            return std::addressof(x);
        }
  
        return m_allocator.address(x);
    }
  
    const_pointer address(const_reference x) const noexcept
    {
        if (pointer_to_internal_buffer(std::addressof(x))) {
            return std::addressof(x);
        }
  
        return m_allocator.address(x);
    }
  
    template <class U>
    struct rebind {
        typedef stack_allocator<U, N, allocator_type> other;
    };
  
    // Buffer pointer declaration
    pointer buffer() const noexcept
    {
        return m_begin;
    }
  
private:
    // Private functions
    bool pointer_to_internal_buffer(const_pointer p) const
    {
        return (!(std::less<const_pointer>()(p, m_begin)) && (std::less<const_pointer>()(p, m_end)));
    }
  
    allocator_type m_allocator;
    pointer m_begin;
    pointer m_end;
    pointer m_stack_pointer;
};
  
template <class T1, std::size_t N, class Allocator, class T2>
bool operator==(const stack_allocator<T1, N, Allocator>& lhs,
                const stack_allocator<T2, N, Allocator>& rhs) noexcept
{
    return lhs.buffer() == rhs.buffer();
}
// class that will return
template <class T1, std::size_t N, class Allocator, class T2>
bool operator!=(const stack_allocator<T1, N, Allocator>& lhs,
                const stack_allocator<T2, N, Allocator>& rhs) noexcept
{
    return !(lhs == rhs);
}


Things that needs to be considered: 

You should think about your allocation habits before using a stack allocator. First, you must think carefully about what it means to allocate and deallocate memory while utilizing a memory buffer on the stack.



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads