我可以假设分配器不直接保存它们的内存池(因此可以复制)吗?

Meh*_*dad 8 c++ stateful allocator c++11

我正在编写一个容器,并希望允许用户使用自定义分配器,但我不知道是否应该通过引用或值传递分配器.

是否保证(或者至少是合理的假设)分配器对象不会直接包含其内存池,因此可以复制分配器并期望分配器的内存池交叉兼容?或者我是否总是需要通过引用传递分配器?

(我发现通过引用传递会损害性能> 2因为编译器开始担心别名,所以它是否可以依赖于这个假设.)

Ker*_* SB 11

旧的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),一个分配器,其值类型与容器的值类型相同.[注意:如果构造函数的调用使用可选allocator参数的默认值,则Allocator类型必须支持值初始化.-end note]此分配器的副本用于由这些构造函数和所有成员函数执行的任何内存分配,在每个容器对象的生存期内或直到替换分配器.分配器只能通过赋值或swap()替换.分配器更换被拷贝赋值,赋值移动或分配器仅当交换进行 allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value, allocator_traits<allocator_type>::propagate_on_container_move_assignment::valueallocator_traits<allocator_type>::propagate_on_container_swap::value相应的容器操作的实现中是真实的.除非被交换的对象具有比较相等或allocator_traits<allocator_type>::propagate_on_container_swap::value为真的分配器,否则调用容器的交换函数的行为是未定义的.在本条款中定义的所有容器类型中,该成员get_allocator()返回用于构造容器的分配器的副本,或者,如果已替换该分配器,则返回最近替换的副本.

另请参阅std::allocator_traits概要文档.


How*_*ant 10

在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
Run Code Online (Sandbox Code Playgroud)

即,当您复制分配器时,两个副本需要能够删除彼此的指针.

可以想象,可以将内部缓冲区放入分配器中,但是副本必须保留其他缓冲区的列表.或者,分配器可能具有不变量,即释放总是无操作,因为指针总是来自内部缓冲区(来自您自己的缓冲区或来自其他副本).

但无论方案如何,副本都必须是"交叉兼容"的.

更新

这是符合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);
}
Run Code Online (Sandbox Code Playgroud)

它可以像这样使用:

#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;
}
Run Code Online (Sandbox Code Playgroud)

上述问题的所有分配均来自本地arena,大小为1 Kb.您应该能够通过值或引用传递此分配器.

  • 不需要调用`v.reserve(100)`.它只是一个空间和时间的优化.如果你不调用`reserve`,你将更快地耗尽堆栈空间,因为`arena`不会尝试回收空间,除非释放是最后分配的东西.当向量从2个元素的空间增长到4(例如)时,在释放2个元素的空间之前分配4个元素的空间.所以前者永远失去了. (3认同)