标准c ++库中有many classes个可能分配内存但不接受分配器的内存。其中一些这样做是因为无法在类型擦除的上下文中分配内存。
一个示例是std ::: std :: any,它的构造函数在其设计的某个时刻接受Allocator自变量,但由于显然unimplementable而被丢弃。我确实考虑了一段时间,并且想知道阻止其实施的确切问题是什么。 无法满足标准的哪些要求?
比方说,我们从any
的基本实现开始。分配内存是微不足道的:
struct any {
struct type_interface;
template <typename T>
struct type_impl;
type_interface* value;
any(T&& value, const Allocator& allocator = Allocator()) {
using actual_allocator_t
= std::allocator_traits<Allocator>::rebind_alloc<type_impl<T>>;
actual_allocator_t actual_allocator;
// do allocate
// do construct
// assign obtained pointer
}
};
问题显然是我们丢失了最初分配对象type_impl<T>
的分配器。一种技巧可能是创建一个方法,该方法声明一个静态变量来存储该分配器。
template <typename Allocator>
auto& get_allocator(const Allocator& allocator) {
using actual_allocator_t = std::allocator_traits<Allocator>::rebind_alloc<type_impl<T>>;
// static variable: initialized just on the first call
static actual_allocator_t actual_allocator(allocator);
return actual_allocator;
}
// and the constructor is now
any::any(T&& value, const Allocator& allocator = Allocator()) {
auto actual_allocator = get_allocator(allocator);
// do allocate
// do construct
// assign obtained pointer
}
现在,我们可以检索相同的对象以解除分配先前分配的对象。最后要解决的问题是解除分配。对象无法释放自身,因此可以使用相同的技巧来包装释放逻辑并通过接口使用它。
// Deallocator interface
struct deallocate_interface{
virtual void deallocate(void*) {};
};
template <typename Allocator>
struct deallocate_wrapper{
virtual void deallocate(void* ptr) {
std::allocator_traits<Allocator>::deallocate(
this->allocator,
reinterpret_cast<typename Allocator::value_type*>(ptr),
1u
);
}
};
以同样的方式,将其存储到静态方法中:
template <typename Allocator>
deallocate_interface& get_deallocator(Allocator& allocator) {
auto& actual_allocator = get_allocator(allocator);
// static variable: initialized just on the first call
static deallocate_wrapper<std::decay_t<decltype(actual_allocator)>> deallocator(actual_allocator);
return deallocator;
}
我看到的唯一限制是该实现对相同类型的所有对象使用相同的分配器,这意味着在复制/移动的情况下,该分配器将不会被复制/移动。 但这不是总比没有分配器好吗? 我在这里(https://github.com/barsan-md/type-erasure-and-allocation)测试了代码,看它是否按预期工作。 可能的输出是:
Begin
Allocator: 0x55ec6563a132, allocating: 0x55ec667e4280
Constructed: 0x55ec667e4280, print: Hello
Allocator: 0x55ec6563a132, allocating: 0x55ec667e42b0
Constructed: 0x55ec667e42b0, print: World
Allocator: 0x55ec6563a140, allocating: 0x55ec667e42e0
Constructed: 0x55ec667e42e0, print: 12345
Destroyed: 0x55ec667e42e0, print: 12345
Allocator: 0x55ec6563a140, deallocating: 0x55ec667e42e0
Destroyed: 0x55ec667e42b0, print: World
Allocator: 0x55ec6563a132, deallocating: 0x55ec667e42b0
Destroyed: 0x55ec667e4280, print: Hello
Allocator: 0x55ec6563a132, deallocating: 0x55ec667e4280
End