免责声明:我编写了一个小型库来探索这个概念:https://github.com/ppetr/refcounted-var-sized-class
我们想要为类型T
的数据结构和类型A
的元素数组分配单个内存块。在大多数情况下,A
将只是char
。
为此,让我们定义一个RAII类来分配和释放这样的内存块。这会带来几个困难:
template <typename T, typename A = char>
class Placement {
public:
explicit Placement(size_t size)
: size_(size),
allocation_(std::allocator<char>().allocate(AllocatedBytes())) {
static_assert(std::is_trivial<Placeholder>::value);
}
Placement(Placement const&) = delete;
Placement(Placement&& other) {
allocation_ = other.allocation_;
size_ = other.size_;
other.allocation_ = nullptr;
}
~Placement() {
if (allocation_) {
std::allocator<char>().deallocate(allocation_, AllocatedBytes());
}
}
T* Node() const { return reinterpret_cast<T*>(&AsPlaceholder()->node); }
A* Array() const { return reinterpret_cast<A*>(&AsPlaceholder()->array); }
size_t Size() { return size_; }
private:
struct Placeholder {
typename std::aligned_storage<sizeof(T), alignof(T)>::type node;
typename std::aligned_storage<sizeof(A[1]), alignof(A[1])>::type array;
};
Placeholder* AsPlaceholder() const {
void* ptr = allocation_;
size_t space = sizeof(Placeholder) + alignof(Placeholder) - 1;
ptr = std::align(alignof(Placeholder), sizeof(Placeholder), ptr, space);
assert(ptr != nullptr);
return reinterpret_cast<Placeholder*>(ptr);
}
size_t AllocatedBytes() {
return sizeof(Placeholder) + alignof(Placeholder) - 1 +
(size_ - 1) * sizeof(A);
}
size_t size_;
char* allocation_;
};
一旦我们解决了内存分配的问题,就可以定义一个包装器类,该类在分配的内存块中初始化 T
和 A
的数组。
template <typename T, typename A = char,
typename std::enable_if<!std::is_destructible<A>{} ||
std::is_trivially_destructible<A>{},
bool>::type = true>
class VarSized {
public:
template <typename... Arg>
VarSized(Placement<T, A> placement, Arg&&... args)
: placement_(std::move(placement)) {
auto [aligned, array] = placement_.Addresses();
array = new (array) char[placement_.Size()];
new (aligned) T(array, placement_.Size(), std::forward<Arg>(args)...);
}
template <typename... Arg>
VarSized(size_t size, Arg&&... args)
: VarSized(Placement<T, A>(size), std::forward<Arg>(args)...) {}
~VarSized() { std::move(*this).Delete(); }
Placement<T, A> Delete() && {
Placement<T, A> placement(std::move(placement_));
(*this)->~T();
return placement;
}
T& operator*() const { return *placement_.Node(); }
const T* operator->() const { return &**this; }
private:
Placement<T, A> placement_;
};
这种类型是可移动的,但显然不可复制。我们可以提供一个函数将其转换为具有自定义删除器的shared_ptr
。但这将需要在内部分配另一个小块内存用于引用计数器(也请参见std::tr1::shared_ptr如何实现?)。
这可以通过引入一个专门的数据类型来解决,该数据类型将在单个结构中保存我们的Placement
、引用计数器和实际数据类型的字段。有关详细信息,请参见我的refcount_struct.h。