我在codereview上问了同样的问题,但是他们恭敬地指出这个问题更适合SO。
请考虑以下代码:
#include<vector>
#include<memory>
template<typename T>
struct S final {
struct B {
virtual void push_back(T&& v) = 0;
virtual ~B() { }
};
template<class Allocator>
struct D final: public B {
D(Allocator alloc): vec{alloc} { }
void push_back(T&& v) override { vec.push_back(v); }
std::vector<T, Allocator> vec;
};
S(): S{std::allocator<T>{}} { }
template<class Allocator>
S(Allocator alloc): ptr{new D<Allocator>{alloc}} { }
~S() { delete ptr; }
void push_back(T&& v) { ptr->push_back(std::move(v)); }
B* ptr;
};
int main() {
int x = 42;
S<int> s1{};
S<double> s2{std::allocator<double>{}};
s1.push_back(42);
s2.push_back(x);
}
这是问题目的的最小例子
想法是 type-erase 接受自定义分配器的东西(在本例中为std::vector
),以便弯曲容器的定义(具有分配器的类型)作为其类型的一部分)类似于std::function
之类的东西(它没有作为其类型的一部分的分配器的类型,但在构造期间仍然接受分配器)。
上面的代码编译,但我怀疑这个类是否按照预期的那样工作
换句话说,每当类的用户提供自己的分配器时,它就被用作新std::vector
的参数,其类型被擦除,但它不用于分配{{1的实例D
指出。
这是有效/逻辑设计,还是应该为每个分配一致地使用分配器? 我的意思是,在STL或其他一些主要的图书馆中也可以找到一些东西,或者它是没有多大意义的东西?
答案 0 :(得分:1)
是的,这是一个有效的设计IF:
您希望内存分配策略是用户定义的,但希望类的接口具有多态性。例如,如果您的某些对象是由消息传递协议生成的,那么这将是合理的。在任何一条消息中,出于性能原因,可能有许多对象可以合理地从同一个内存块(由消息拥有)中分配。
然而:
显然你想要在智能指针方面实现ptr,或者非常谨慎地编写所有的复制/移动构造函数/运算符。
您需要非常仔细地管理从一个类型擦除的容器到另一个容器的复制(并且肯定会移动!)对象。例如,允许将分配器A分配的对象移动到由分配器B管理的容器中可能是无效的。这种事情很快就难以推理。需要进行运行时检查,如果违反(或者可能降级为副本?),可能会抛出std::logic_error
,等等。
答案 1 :(得分:1)
没有正确的答案,设计是合理的,但使用用户提供的分配器来创建派生对象也是合理的。为此,您需要在类型擦除的上下文中进行销毁和释放,因此可以使用分配器:
template<typename T>
struct S final {
struct B {
// ...
virtual void destroy() = 0;
protected:
virtual ~B() { }
};
template<class Allocator>
struct D final: public B {
// ...
void destroy() override {
using A2 = std::allocator_traits<Allocator>::rebind_alloc<D>;
A2 a{vec.get_allocator()};
this->~D();
a2.deallocate(this, 1);
}
};
S(): S{std::allocator<T>{}} { }
template<class Allocator>
S(Allocator alloc): ptr{nullptr} {
using DA = D<Allocator>;
using AT = std::allocator_traits<Allocator>;
static_assert(std::is_same<typename AT::pointer, typename AT::value_type*>::value, "Allocator doesn't use fancy pointers");
using A2 = AT::rebind_alloc<DA>;
A2 a2{alloc};
auto p = a2.allocate(1);
try {
ptr = ::new((void*)p) DA{alloc};
} catch (...) {
a2.deallocate(p);
throw;
}
}
~S() { ptr->destroy(); }
// ...
};
(此代码断言Allocator::pointer
为Allocator::value_type*
,以支持那些不正确的分配器,您需要使用pointer_traits
来转换指针类型,这是剩下的作为读者的练习。)