自毁类型的擦除 类 像 std::function 是如何实现的?
How are self-destructing type erasure classes like std::function implemented?
我想了解 std::function
的实施方式。为简单起见,让我们考虑不带参数的仅移动函数。
我了解 std::function
通过典型的类型擦除技术擦除其目标的类型:
template<class Result>
struct function
{
public:
template<class Function>
function(Function&& f)
: f_(std::make_unique<callable_base>(std::forward<Function>(f)))
{}
// XXX how to implement constructor with allocator?
template<class Alloc, class Function>
function(const Alloc& alloc, Function&& f);
Result operator()() const
{
return (*f_)();
}
private:
struct callable_base
{
virtual Result operator()() const = 0;
virtual ~callable_base(){}
};
template<class Function>
struct callable
{
mutable Function f;
virtual Result operator()() const
{
return f;
}
};
// XXX what should the deleter used below do?
struct deleter;
std::unique_ptr<callable_base, deleter> f_;
};
我想扩展此类型的功能以支持自定义分配。我需要清除分配器的类型,但是使用 std::unique_ptr
很难做到这一点。提供给 unique_ptr
的自定义删除器需要知道提供给构造函数的 Function
的具体类型,以便能够正确地释放其存储空间。我可以使用另一个 unique_ptr
来键入擦除删除器,但该解决方案是循环的。
callable<Function>
似乎需要自行解除分配。正确的方法是什么?如果我在 callable<Function>
的析构函数内部解除分配,那似乎太早了,因为它的成员还活着。
我认为不可能以可移植的方式做到这一点,因为 仅 提供的内存管理分配器。
我一直在寻找 std::shared_ptr
的实现,因为它支持 type erasure also for it's deleter and allocator (see overload 6): In an sketch implementation I found around here 有一个辅助对象用于存储这些副本,但是这个对象是使用 operator new
分配的,并且使用 operator delete
释放,从而绕过提供的分配器和删除器。
我正在考虑使用分配器的临时副本(在堆栈上)以释放存储的分配器(从中创建副本)以及存储的对象。问题是:当您不知道类型时,如何在不使用 new
/delete
的情况下获得副本?不幸的是,covariance is ruled out 通过这个(需要 return 一个指针)。
现在,我们来看看非标准解决方案:如果您习惯使用 alloca
or variable length arrays,那么您可以使用删除器在堆栈上创建足够大小的内存区域,并让存储的分配器在该内存中创建自己的副本。这个堆栈分配(因此自动存储持续时间)副本可以释放存储的分配器和存储的对象,并最终被删除函数销毁(这就是所有这一切的重点,不知道分配器的具体类型).草图:
struct aux_base {
// also provide access to stored function
virtual size_t my_size(void) const = 0;
virtual aux_base * copy_in(void * memory) const = 0;
virtual void free(void * ptr) = 0;
virtual ~aux_base() {}
};
template<class Alloc, class Function>
struct aux : public aux_base {
// Store allocator and function here
size_t my_size(void) const {
return sizeof(*this);
}
aux_base * copy_in(void * memory) const {
// attention for alignment issues!
return new (memory) aux(*this);
}
void free(void * ptr) {
aux * stored = reinterpret_cast<aux *>(ptr);
// do your stuff
}
};
void deleter_for_aux(aux_base * ptr) {
char memory[ptr->my_size()];
aux_base * copy = ptr->copy_in(memory);
copy->free(ptr);
copy->~aux_base(); // call destructor
}
就是说,如果 是 一种在标准 C++ 中执行此操作的方法,而不依赖于提供的分配器以外的其他动态内存源,我会很高兴知道它! :)
这是我得出的近似值。我不认为它是完全正确的,但它适用于我的用例。
想法是将 "no-op" 删除器与 unique_ptr
一起使用。删除器调用对象的析构函数但不释放其存储空间。对象通过回调在其析构函数中自行释放。
template<class Result>
struct function
{
public:
template<class Function>
function(Function&& f)
: f_(std::make_unique<callable_base>(std::forward<Function>(f)))
{}
template<class Alloc, class Function>
function(const Alloc& alloc, Function&& f)
: f_(allocate_unique(alloc, std::forward<Function>(f)))
{}
Result operator()() const
{
return (*f_)();
}
private:
struct callable_base
{
// a deallocation callback to use within our destructor
using deallocate_function_type = void(*)(callable_base*);
deallocate_function_type deallocate_function;
template<class Function>
callable_base(Function callback)
: deallocate_function(callback)
{}
virtual Result operator()() const = 0;
virtual ~callable_base()
{
// deallocate this object's storage with the callback
deallocate_function(this);
}
};
template<class Alloc, class Function>
struct callable : callable_base
{
mutable Function f;
callable(Function&& f)
: callable_base(deallocate),
f(std::forward<Function>(f))
{}
virtual Result operator()() const
{
return f;
}
static void deallocate(callable_base* ptr)
{
// upcast to the right type of pointer
callable* self = static_cast<callable*>(ptr);
// XXX it seems like creating a new allocator here is cheating
// instead, we should use some member allocator, but it's
// not clear where to put it
Alloc alloc;
alloc.deallocate(self);
}
};
struct self_deallocator_deleter
{
template<class T>
void operator()(T* ptr) const
{
// call T's destructor but do not deallocate ptr
ptr->~T();
}
};
template<class Alloc, class Function>
static std::unique_ptr<callable_base, self_deallocator_deleter>
allocate_unique(const Alloc& alloc, Function&& f)
{
// allocate and construct the concrete callable object
auto f_ptr = std::allocator_traits<Alloc>::allocate(alloc, 1);
std::allocator_traits<Alloc>::construct(f_ptr, std::forward<Function>(f));
// return the pointer through a unique_ptr
return std::unique_ptr<callable_base,self_deallocator_deleter>(f_ptr);
}
std::unique_ptr<callable_base, self_deallocator_deleter> f_;
};
如果给定的分配器成为 callable
对象的成员而不是在 callable::deallocate
内部动态创建新的分配器对象,则解决方案会更好。问题是我们不能使分配器成为 callable
的成员,因为 callable
对象在调用 callable::deallocate
时不再存在。
std::function
在 C++17 中丢失了分配器,部分原因是类型擦除分配器存在问题。但是,一般模式是将分配器重新绑定到您用于执行类型擦除的任何类型,将原始分配器存储在类型擦除的东西中,并在删除类型擦除的东西时再次重新绑定分配器。
template<class Ret, class... Args>
struct Call_base {
virtual Ret Call(Args&&...);
virtual void DeleteThis();
protected:
~Call_base() {}
};
template<class Allocator, class Fx, class Ret, class... Args>
struct Call_fn : Call_base<Ret, Args...> {
Allocator a;
decay_t<Fx> fn;
Call_fn(Allocator a_, Fx&& fn_)
: a(a_), fn(forward<Fx>(fn_))
{}
virtual Ret Call(Args&& vals) override {
return invoke(fn, forward<Args>(vals)...);
}
virtual void DeleteThis() override {
// Rebind the allocator to an allocator to Call_fn:
using ReboundAllocator = typename allocator_traits<Allocator>::
template rebind_alloc<Call_fn>;
ReboundAllocator aRebound(a);
allocator_traits<ReboundAllocator>::destroy(aRebound, this);
aRebound.deallocate(this, 1);
}
};
template<class Allocator, class Fx, class Ret, class... Args>
Call_base<Ret, Args...> * Make_call_fn(Allocator a, Fx&& fn) {
using TypeEraseType = Call_fn<Allocator, Fx, Ret, Args...>;
using ReboundAllocator = typename allocator_traits<Allocator>::
template rebind_alloc<TypeEraseType>;
ReboundAllocator aRebound(a);
auto ptr = aRebound.allocate(1); // throws
try {
allocator_traits<ReboundAllocator>::construct(aRebound, ptr, a, forward<Fx>(fn));
} catch (...) {
aRebound.deallocate(ptr, 1);
throw;
}
return ptr;
}
我想了解 std::function
的实施方式。为简单起见,让我们考虑不带参数的仅移动函数。
我了解 std::function
通过典型的类型擦除技术擦除其目标的类型:
template<class Result>
struct function
{
public:
template<class Function>
function(Function&& f)
: f_(std::make_unique<callable_base>(std::forward<Function>(f)))
{}
// XXX how to implement constructor with allocator?
template<class Alloc, class Function>
function(const Alloc& alloc, Function&& f);
Result operator()() const
{
return (*f_)();
}
private:
struct callable_base
{
virtual Result operator()() const = 0;
virtual ~callable_base(){}
};
template<class Function>
struct callable
{
mutable Function f;
virtual Result operator()() const
{
return f;
}
};
// XXX what should the deleter used below do?
struct deleter;
std::unique_ptr<callable_base, deleter> f_;
};
我想扩展此类型的功能以支持自定义分配。我需要清除分配器的类型,但是使用 std::unique_ptr
很难做到这一点。提供给 unique_ptr
的自定义删除器需要知道提供给构造函数的 Function
的具体类型,以便能够正确地释放其存储空间。我可以使用另一个 unique_ptr
来键入擦除删除器,但该解决方案是循环的。
callable<Function>
似乎需要自行解除分配。正确的方法是什么?如果我在 callable<Function>
的析构函数内部解除分配,那似乎太早了,因为它的成员还活着。
我认为不可能以可移植的方式做到这一点,因为 仅 提供的内存管理分配器。
我一直在寻找 std::shared_ptr
的实现,因为它支持 type erasure also for it's deleter and allocator (see overload 6): In an sketch implementation I found around here 有一个辅助对象用于存储这些副本,但是这个对象是使用 operator new
分配的,并且使用 operator delete
释放,从而绕过提供的分配器和删除器。
我正在考虑使用分配器的临时副本(在堆栈上)以释放存储的分配器(从中创建副本)以及存储的对象。问题是:当您不知道类型时,如何在不使用 new
/delete
的情况下获得副本?不幸的是,covariance is ruled out 通过这个(需要 return 一个指针)。
现在,我们来看看非标准解决方案:如果您习惯使用 alloca
or variable length arrays,那么您可以使用删除器在堆栈上创建足够大小的内存区域,并让存储的分配器在该内存中创建自己的副本。这个堆栈分配(因此自动存储持续时间)副本可以释放存储的分配器和存储的对象,并最终被删除函数销毁(这就是所有这一切的重点,不知道分配器的具体类型).草图:
struct aux_base {
// also provide access to stored function
virtual size_t my_size(void) const = 0;
virtual aux_base * copy_in(void * memory) const = 0;
virtual void free(void * ptr) = 0;
virtual ~aux_base() {}
};
template<class Alloc, class Function>
struct aux : public aux_base {
// Store allocator and function here
size_t my_size(void) const {
return sizeof(*this);
}
aux_base * copy_in(void * memory) const {
// attention for alignment issues!
return new (memory) aux(*this);
}
void free(void * ptr) {
aux * stored = reinterpret_cast<aux *>(ptr);
// do your stuff
}
};
void deleter_for_aux(aux_base * ptr) {
char memory[ptr->my_size()];
aux_base * copy = ptr->copy_in(memory);
copy->free(ptr);
copy->~aux_base(); // call destructor
}
就是说,如果 是 一种在标准 C++ 中执行此操作的方法,而不依赖于提供的分配器以外的其他动态内存源,我会很高兴知道它! :)
这是我得出的近似值。我不认为它是完全正确的,但它适用于我的用例。
想法是将 "no-op" 删除器与 unique_ptr
一起使用。删除器调用对象的析构函数但不释放其存储空间。对象通过回调在其析构函数中自行释放。
template<class Result>
struct function
{
public:
template<class Function>
function(Function&& f)
: f_(std::make_unique<callable_base>(std::forward<Function>(f)))
{}
template<class Alloc, class Function>
function(const Alloc& alloc, Function&& f)
: f_(allocate_unique(alloc, std::forward<Function>(f)))
{}
Result operator()() const
{
return (*f_)();
}
private:
struct callable_base
{
// a deallocation callback to use within our destructor
using deallocate_function_type = void(*)(callable_base*);
deallocate_function_type deallocate_function;
template<class Function>
callable_base(Function callback)
: deallocate_function(callback)
{}
virtual Result operator()() const = 0;
virtual ~callable_base()
{
// deallocate this object's storage with the callback
deallocate_function(this);
}
};
template<class Alloc, class Function>
struct callable : callable_base
{
mutable Function f;
callable(Function&& f)
: callable_base(deallocate),
f(std::forward<Function>(f))
{}
virtual Result operator()() const
{
return f;
}
static void deallocate(callable_base* ptr)
{
// upcast to the right type of pointer
callable* self = static_cast<callable*>(ptr);
// XXX it seems like creating a new allocator here is cheating
// instead, we should use some member allocator, but it's
// not clear where to put it
Alloc alloc;
alloc.deallocate(self);
}
};
struct self_deallocator_deleter
{
template<class T>
void operator()(T* ptr) const
{
// call T's destructor but do not deallocate ptr
ptr->~T();
}
};
template<class Alloc, class Function>
static std::unique_ptr<callable_base, self_deallocator_deleter>
allocate_unique(const Alloc& alloc, Function&& f)
{
// allocate and construct the concrete callable object
auto f_ptr = std::allocator_traits<Alloc>::allocate(alloc, 1);
std::allocator_traits<Alloc>::construct(f_ptr, std::forward<Function>(f));
// return the pointer through a unique_ptr
return std::unique_ptr<callable_base,self_deallocator_deleter>(f_ptr);
}
std::unique_ptr<callable_base, self_deallocator_deleter> f_;
};
如果给定的分配器成为 callable
对象的成员而不是在 callable::deallocate
内部动态创建新的分配器对象,则解决方案会更好。问题是我们不能使分配器成为 callable
的成员,因为 callable
对象在调用 callable::deallocate
时不再存在。
std::function
在 C++17 中丢失了分配器,部分原因是类型擦除分配器存在问题。但是,一般模式是将分配器重新绑定到您用于执行类型擦除的任何类型,将原始分配器存储在类型擦除的东西中,并在删除类型擦除的东西时再次重新绑定分配器。
template<class Ret, class... Args>
struct Call_base {
virtual Ret Call(Args&&...);
virtual void DeleteThis();
protected:
~Call_base() {}
};
template<class Allocator, class Fx, class Ret, class... Args>
struct Call_fn : Call_base<Ret, Args...> {
Allocator a;
decay_t<Fx> fn;
Call_fn(Allocator a_, Fx&& fn_)
: a(a_), fn(forward<Fx>(fn_))
{}
virtual Ret Call(Args&& vals) override {
return invoke(fn, forward<Args>(vals)...);
}
virtual void DeleteThis() override {
// Rebind the allocator to an allocator to Call_fn:
using ReboundAllocator = typename allocator_traits<Allocator>::
template rebind_alloc<Call_fn>;
ReboundAllocator aRebound(a);
allocator_traits<ReboundAllocator>::destroy(aRebound, this);
aRebound.deallocate(this, 1);
}
};
template<class Allocator, class Fx, class Ret, class... Args>
Call_base<Ret, Args...> * Make_call_fn(Allocator a, Fx&& fn) {
using TypeEraseType = Call_fn<Allocator, Fx, Ret, Args...>;
using ReboundAllocator = typename allocator_traits<Allocator>::
template rebind_alloc<TypeEraseType>;
ReboundAllocator aRebound(a);
auto ptr = aRebound.allocate(1); // throws
try {
allocator_traits<ReboundAllocator>::construct(aRebound, ptr, a, forward<Fx>(fn));
} catch (...) {
aRebound.deallocate(ptr, 1);
throw;
}
return ptr;
}