Пользовательский распределитель с жесткими ограничениями

Я хочу заменить некоторый код, использующий boost::interprocess разделяемую память. Одним из преимуществ разделяемой памяти является то, что вы можете наложить ограничения на максимальный объем используемой памяти. Я ищу собственный распределитель, основанный на std::allocator, который может это сделать.

Только определенные классы в программе будут использовать этот распределитель, все остальное использует значение по умолчанию std::allocator и ограничено только доступной оперативной памятью.

Я пытаюсь написать один из своих собственных, но сталкиваюсь с проблемами, в основном с тем, как разделить состояние между копиями распределителя, созданными контейнерами STL. Состояние включает количество оставшихся свободных байтов и максимальный размер, который может использовать распределитель. Я думал, что смогу сойти с рук, сделав их thread_local, но тогда несколько разных экземпляров одного и того же класса будут выделять и освобождать память из одной и той же ограниченной кучи, а это не то, чего я хочу. Я начинаю думать, что это невозможно, отсюда и этот вопрос. На данный момент ни непрерывное распределение, ни производительность не являются основными требованиями.

Жесткое ограничение на объем памяти также не может быть параметром шаблона, оно считывается из конфигурационного файла.

Изменить. Проблема с состоянием совместного использования заключается в том, что некоторые контейнеры вызывают конструктор по умолчанию типа распределителя. Очевидно, что этот конструктор не может ничего знать о внешнем мире, даже если используется shared_ptr, он будет инициализирован nullptr. Например, посмотрите исходный код для std::string::clear

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


person James    schedule 09.08.2015    source источник
comment
Что не так с обычной поддержкой распределителя с сохранением состояния в stdlib?:   -  person Puppy    schedule 10.08.2015
comment
@Puppy Насколько я понимаю, распределители копируются, поэтому, если в первой копии остается 50 байтов во время копирования, а затем выделяется еще 10 байтов, вторая копия все равно будет думать, что осталось 50. Я мог бы сделать что-то совершенно глупое.   -  person James    schedule 10.08.2015
comment
Вы разделяете состояние между распределителями так же, как и между любыми другими объектами. Пусть каждый распределитель хранит указатель или, возможно, shared_ptr на объект, управляющий указанным состоянием.   -  person Igor Tandetnik    schedule 10.08.2015
comment
Да оказывается я туплю   -  person James    schedule 10.08.2015


Ответы (1)


Следуя приведенным выше советам, я придумал это, которое, кажется, работает нормально для типов POD, но все разваливается, когда я пытаюсь создать вектор или карту, использующую String:

#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;
};
person James    schedule 10.08.2015