Могу ли я предположить, что распределители не хранят свой пул памяти напрямую (и, следовательно, их можно скопировать)?

StackOverflow https://stackoverflow.com//questions/11703643

  •  13-12-2019
  •  | 
  •  

Вопрос

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

Гарантировано ли (или, по крайней мере, разумное предположение), что объект распределителя будет нет содержать его пул памяти напрямую, и, следовательно, было бы нормально скопировать распределитель и ожидать, что пулы памяти распределителей будут перекрестно совместимы?Или мне всегда нужно передавать распределители по ссылке?

(Я обнаружил, что передача по ссылке ухудшает производительность более чем в 2 раза, потому что компилятор начинает беспокоиться о псевдонимах, поэтому он решает, могу ли я полагаться на это предположение.)

Это было полезно?

Решение

в C ++ 11 Раздел 17.6.3.5 Требования распределения [alloCator.requirements] Определяет требования для соответствия распределителям. Среди требований:

X                    an Allocator class for type T
...
a, a1, a2            values of type X&
...
a1 == a2             bool          returns true only if storage
                                   allocated from each can be
                                   deallocated via the other.
                                   operator== shall be reflexive,
                                   symmetric, and transitive, and
                                   shall not exit via an exception.
...
X a1(a);                           Shall not exit via an exception.
                                   post: a1 == a
.

I.e. Когда вы копируете распределитель, два копии необходимы для удаления указателей друг друга.

Возможно, можно вставить внутренние буферы в распределителей, но копии должны будут сохранять список буферов других. Или, возможно, распределитель может иметь инвариант, что DealLocation всегда является NO-OP, потому что указатель всегда происходит от внутреннего буфера (либо от вашего собственного, либо из какой-либо другой копии).

Но независимо от схемы, копии должны быть «совместимыми».

<Сильное> Обновление

Вот C ++ 11 соответствующий распределитель, который делает «оптимизацию короткой строки». Чтобы сделать его C ++ 11, соответствующий, мне пришлось поставить «внутренний» буфер наружным на распределитель, чтобы копии равали:

#include <cstddef>

template <std::size_t N>
class arena
{
    static const std::size_t alignment = 16;
    alignas(alignment) char buf_[N];
    char* ptr_;

    std::size_t 
    align_up(std::size_t n) {return n + (alignment-1) & ~(alignment-1);}

public:
    arena() : ptr_(buf_) {}
    arena(const arena&) = delete;
    arena& operator=(const arena&) = delete;

    char* allocate(std::size_t n)
    {
        n = align_up(n);
        if (buf_ + N - ptr_ >= n)
        {
            char* r = ptr_;
            ptr_ += n;
            return r;
        }
        return static_cast<char*>(::operator new(n));
    }
    void deallocate(char* p, std::size_t n)
    {
        n = align_up(n);
        if (buf_ <= p && p < buf_ + N)
        {
            if (p + n == ptr_)
                ptr_ = p;
        }
        else
            ::operator delete(p);
    }
};

template <class T, std::size_t N>
class stack_allocator
{
    arena<N>& a_;
public:
    typedef T value_type;

public:
    template <class U> struct rebind {typedef stack_allocator<U, N> other;};

    explicit stack_allocator(arena<N>& a) : a_(a) {}
    template <class U>
        stack_allocator(const stack_allocator<U, N>& a)
            : a_(a.a_) {}
    stack_allocator(const stack_allocator&) = default;
    stack_allocator& operator=(const stack_allocator&) = delete;

    T* allocate(std::size_t n)
    {
        return reinterpret_cast<T*>(a_.allocate(n*sizeof(T)));
    }
    void deallocate(T* p, std::size_t n)
    {
        a_.deallocate(reinterpret_cast<char*>(p), n*sizeof(T));
    }

    template <class T1, std::size_t N1, class U, std::size_t M>
    friend
    bool
    operator==(const stack_allocator<T1, N1>& x, const stack_allocator<U, M>& y);

    template <class U, std::size_t M> friend class stack_allocator;
};

template <class T, std::size_t N, class U, std::size_t M>
bool
operator==(const stack_allocator<T, N>& x, const stack_allocator<U, M>& y)
{
    return N == M && &x.a_ == &y.a_;
}

template <class T, std::size_t N, class U, std::size_t M>
bool
operator!=(const stack_allocator<T, N>& x, const stack_allocator<U, M>& y)
{
    return !(x == y);
}
.

Это можно использовать как это:

#include <vector>

template <class T, std::size_t N> using A = stack_allocator<T, N>;
template <class T, std::size_t N> using Vector = std::vector<T, stack_allocator<T, N>>;

int main()
{
    const std::size_t N = 1024;
    arena<N> a;
    Vector<int, N> v{A<int, N>(a)};
    v.reserve(100);
    for (int i = 0; i < 100; ++i)
        v.push_back(i);
    Vector<int, N> v2 = std::move(v);
    v = v2;
}
.

Все распределения для вышеуказанной задачи нарисованы от локального генеракодицетагкода, который размером 1 кБ. Вы должны быть в состоянии передать этот распределитель по значению или по ссылке.

Другие советы

Старый стандарт C++ предъявляет требования к совместимому со стандартом распределителю:Эти требования включают в себя следующее: если у вас есть Alloc<T> a, b, затем a == b, и вы можете использовать b освободить вещи, которые были выделены с помощью a.Распределители по своей сути лицо без гражданства.


В C++11 ситуация стала намного сложнее, поскольку теперь есть поддержка обладающий состоянием распределители.Когда вы копируете и перемещаете объекты, существуют определенные правила, можно ли скопировать или переместить один контейнер из другого контейнера, если распределители различаются, и как распределители копируются или перемещаются.

Просто чтобы сначала ответить на ваш вопрос:Нет, ты определенно можешь нет предположим, что имеет смысл скопировать ваш распределитель, а ваш распределитель может быть даже не копируемым.

Вот 23.2.1/7 на эту тему:

Если не указано иное, все контейнеры, определенные в этом разделе, получают память с помощью распределителя (см. 17.6.3.5).Конструкторы копирования для этих типов контейнеров получают распределитель, вызывая allocator_traits<allocator_-type>::select_on_container_copy_construction по своим первым параметрам.Конструкторы перемещения получают распределитель путем конструкции перемещения из распределителя, принадлежащего перемещаемому контейнеру.Такая конструкция перемещения распределителя не должна завершаться через исключение.Все остальные конструкторы для этих типов контейнеров принимают Allocator& аргумент (17.6.3.5), распределитель, тип значения которого совпадает с типом значения контейнера.[Примечание:Если при вызове конструктора используется значение по умолчанию необязательного аргумента распределителя, то тип распределителя должен поддерживать инициализацию значения.—конец примечания] Копия этого распределителя используется для любого выделения памяти, выполняемого этими конструкторами и всеми функциями-членами в течение срока службы каждого объекта-контейнера или до тех пор, пока распределитель не будет заменен.Ассистент может быть заменен только через назначение или Swap ().Замена распределителя выполняется путем назначения копирования, перемещения или замены распределителя только в том случае, если allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value, allocator_traits<allocator_type>::propagate_on_container_move_assignment::value, или allocator_traits<allocator_type>::propagate_on_container_swap::value истинно в рамках реализации соответствующей операции контейнера.Поведение вызова функции подкачки контейнера не определено, если только заменяемые объекты не имеют распределителей, которые сравнивают равные или allocator_traits<allocator_type>::propagate_on_container_swap::value правда.Во всех типах контейнеров, определенных в этом пункте, член get_allocator() возвращает копию распределителя, использованного для создания контейнера, или, если этот распределитель был заменен, копию самой последней замены.

См. также документацию std::allocator_traits для конспекта.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top