我正在编写一个容器并希望允许用户使用自定义分配器,但我不知道我应该通过引用还是通过值来传递分配器。
是否保证(或至少做出合理的假设)分配器对象不直接包含其内存池,因此复制分配器并期望内存是可以的分配器的池是交叉兼容的?还是我总是需要通过引用传递分配器?
(我发现通过引用传递会损害性能 > 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/