c++ - 我可以假设分配器不直接持有它们的内存池(因此可以被复制)吗?

标签 c++ c++11 allocator stateful

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

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

(我发现通过引用传递会损害性能 > 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

即当您复制一个分配器时,这两个拷贝需要能够删除彼此的指针。

可以想象,可以将内部缓冲区放入分配器,但拷贝必须保留其他缓冲区的列表。或者分配器可能有一个不变量,即解除分配始终是空操作,因为指针总是来自内部缓冲区(来自您自己的缓冲区或来自其他拷贝)。

但无论采用何种方案,拷贝都必须“交叉兼容”。

更新

这是一个符合 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 Kb 的本地 arena 中提取的。您应该能够按值或按引用传递此分配器。

关于c++ - 我可以假设分配器不直接持有它们的内存池(因此可以被复制)吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11703643/

相关文章:

c++ - 在此代码中使用 memory_order_relaxed 是否正确?

c++ - 动态 vector 的特征库内存使用

go - 堆栈内存和mcache之间的差异或关系?

c++ - 混合 C 和 C++ 全局变量

c++ - 在 C++ 中编写 for/else 的简洁方法?

c++ - 从数组初始化 vector 出错

c++ - 可以创建 N 个方法的类接口(interface)

c++ - 无法从套接字中正确读取

c++ - 遍历 boost 属性树时变量变空

c++ - 为什么 std::allocator 要求 propagate_on_container_move_assignment 为真?