具有硬限制的自定义分配器

时间:2015-08-09 22:01:18

标签: c++11 allocator

我想替换一些使用boost::interprocess共享内存的代码。共享内存的一个优点是可以对可以使用的最大内存量施加限制。我正在寻找一个基于std::allocator的自定义分配器,可以做到这一点。

只有程序中的特定类才会使用此分配器,其他所有类都使用默认的std::allocator,并且仅受可用RAM的限制。

我试图编写自己的一个,但我遇到了问题,主要是如何在STL容器创建的分配器副本之间共享状态。 State包括剩余的空闲字节数和分配器可以使用的最大大小。我认为我可以让它们成为thread_local但是然后同一个类的几个不同实例将从相同的有限堆中分配和释放,这不是我想要的。我开始认为这是不可能的,所以这里有这个问题。目前,连续分配和性能都不是主要要求。

内存大小的硬限制也不能是模板参数,它是从配置文件中读取的。

编辑:共享状态的问题是某些容器调用分配器类型的默认构造函数。显然,即使使用shared_ptr,这个构造函数也不能轻易地了解外部世界,它将被nullptr初始化。例如,查看std::string::clear

的源代码

g++ (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4

1 个答案:

答案 0 :(得分:1)

按照上面的提示后我想出了这个似乎适用于POD类型的东西,但是当我尝试制作使用String的Vector或Map时,事情就崩溃了:

#include <string>
#include <vector>
#include <map>
#include <atomic>
#include <memory>

struct SharedState
{
    SharedState()
    : m_maxSize(0),
      m_bytesRemaining(0)
    {
    }

    SharedState(std::size_t maxSize)
        : m_maxSize(maxSize),
          m_bytesRemaining(maxSize)
    {
    }

    void allocate(std::size_t bytes) const {
        if (m_bytesRemaining < bytes) {
            throw std::bad_alloc();
        }

        m_bytesRemaining -= bytes;
    }

    void deallocate(std::size_t bytes) const {
        m_bytesRemaining += bytes;
    }

    std::size_t getBytesRemaining() const {
        return m_bytesRemaining;
    }

    const std::size_t m_maxSize;
    mutable std::atomic<std::size_t> m_bytesRemaining;
};


// --------------------------------------
template <typename T>
class BaseLimitedAllocator : public std::allocator<T>
{
public:
    using size_type =  std::size_t;
    using pointer = T*;
    using const_pointer = const T*;
    using propagate_on_container_move_assignment = std::true_type;

    template <typename U>
    struct rebind
    {
        typedef BaseLimitedAllocator<U> other;
    };

    BaseLimitedAllocator() noexcept = default;

    BaseLimitedAllocator(std::size_t maxSize) noexcept
    :  m_state(new SharedState(maxSize)) {
    }

    BaseLimitedAllocator(const BaseLimitedAllocator& other) noexcept {
       m_state = other.m_state;
    }

    template <typename U>
    BaseLimitedAllocator(const BaseLimitedAllocator<U>& other) noexcept {
        m_state = other.m_state;
    }

    pointer allocate(size_type n, const void* hint = nullptr) {
        m_state->allocate(n * sizeof(T));
        return std::allocator<T>::allocate(n, hint);
    }

    void deallocate(pointer p, size_type n) {
        std::allocator<T>::deallocate(p, n);
        m_state->deallocate(n * sizeof(T));
    }

public:
    std::shared_ptr<SharedState> m_state;   // This must be public for the rebind copy constructor.
};

template <typename T, typename U>
inline bool operator==(const BaseLimitedAllocator<T>&, const BaseLimitedAllocator<U>&) {
    return true;
}

template <typename T, typename U>
inline bool operator!=(const BaseLimitedAllocator<T>&, const BaseLimitedAllocator<U>&) {
    return false;
}


struct LimitedAllocator : public BaseLimitedAllocator<char>
{
    LimitedAllocator(std::size_t maxSize)
    :  BaseLimitedAllocator<char>(maxSize) {
    }

    template <typename U>
    using Other = typename BaseLimitedAllocator<char>::template rebind<U>::other;
};

// -----------------------------------------
// Example usage:

class SomeClass
{
public:
    using String = std::basic_string<char, std::char_traits<char>, LimitedAllocator::Other<char>>;

    template <typename T>
    using Vector = std::vector<T, LimitedAllocator::Other<T>>;

    template <typename K, typename V>
    using Map = std::map<K, V, std::less<K>, LimitedAllocator::Other<std::pair<const K, V>>>;

    Complex()
    :  allocator(256),
       s(allocator),
       v(allocator),
       m(std::less<int>(), allocator) // Cannot only specify the allocator. Annoying.
    {
    }

    const LimitedAllocator allocator;
    String s;
    Vector<int> v;
    Map<int, String> m;
};