std::barrier
不支持多态的 CompletionFunction
类型/值 -- 它没有在其他类型中找到的适配构造函数。因此,在那里使用 std::function
是不可行的,无论它的支持如何 -- 具有不同 CompletionFunction
类型的两个 std::barrier
是不相关的,不能相互赋值。
您可以自己进行类型擦除,并编写一个 poly_barrier
。这样做的障碍(双关语)是 arrival_token
似乎不支持多态性;在不同的 std::barrier<X>
情况下,可能没有保证它是相同的类型。
然而,它是可移动构造、可移动分配和可销毁的,因此我们也可以对其进行类型擦除。
第一个 API 草图:
struct poly_barrier;
struct poly_barrier_vtable;
struct poly_arrival_token:private std::unique_ptr<void> {
friend struct poly_barrier_vtable;
poly_arrival_token(poly_arrival_token&&)=default;
private:
explicit poly_arrival_token(std::unique_ptr<void> ptr):
poly_arrival_token(std::move(ptr))
{}
};
struct poly_barrier_vtable;
struct poly_barrier {
template<class CF>
poly_barrier( std::ptrdiff_t expected, CF cf );
~poly_barrier();
poly_barrier& operator=(poly_barrier const&)=delete;
poly_arrival_token arrive( std::ptrdiff_t n = 1 );
void wait( poly_arrival_token&& ) const;
void arrive_and_wait();
void arrive_and_drop();
private:
poly_barrier_vtable const* vtable = nullptr;
std::unique_ptr<void> state;
};
现在我们编写一个虚函数表:
struct poly_barrier_vtable {
void(*dtor)(void*) = 0;
poly_arrival_token(*arrive)(void*, std::ptrdiff_t) = 0;
void(*wait)(void const*, poly_arrival_token&& ) = 0;
void(*arrive_and_wait)(void*) = 0;
void(*arrive_and_drop)(void*) = 0;
private:
template<class CF>
static poly_arrival_token make_token( std::barrier<CF>::arrival_token token ) {
return poly_arrival_token(std::make_unique<decltype(token)>(std::move(token)));
}
template<class CF>
static std::barrier<CF>::arrival_token extract_token( poly_arrival_token token ) {
return std::move(*static_cast<std::barrier<CF>::arrival_token*>(token.get()));
}
protected:
template<class CF>
poly_barrier_vtable create() {
using barrier = std::barrier<CF>;
return {
+[](void* pb){
return static_cast<barrier*>(pb)->~barrier();
},
+[](void* pb, std::ptrdiff_t n)->poly_arrival_token{
return make_token<CF>(static_cast<barrier*>(pb)->arrive(n));
},
+[](void const* bp, poly_arrival_token&& token)->void{
return static_cast<barrier const*>(pb)->wait( extract_token<CF>(std::move(token)) );
},
+[](void* pb)->void{
return static_cast<barrier*>(pb)->arrive_and_wait();
},
+[](void* pb)->void{
return static_cast<barrier*>(pb)->arrive_and_drop();
}
};
}
public:
template<class CF>
poly_barrier_vtable const* get() {
static auto const table = create<CF>();
return &table;
}
};
然后我们使用:
struct poly_barrier {
template<class CF>
poly_barrier( std::ptrdiff_t expected, CF cf ):
vtable(poly_barrier_vtable<CF>::get()),
state(std::make_unique<std::barrier<CF>>( expected, std::move(cf) ))
{}
~poly_barrier() {
if (vtable) vtable->dtor(state.get());
}
poly_barrier& operator=(poly_barrier const&)=delete;
poly_arrival_token arrive( std::ptrdiff_t n = 1 ) {
return vtable->arrive( state.get(), n );
}
void wait( poly_arrival_token&& token ) const {
return vtable->wait( state.get(), std::move(token) );
}
void arrive_and_wait() {
return vtable->arrive_and_wait(state.get());
}
void arrive_and_drop() {
return vtable->arrive_and_drop(state.get());
}
private:
poly_barrier_vtable const* vtable = nullptr;
std::unique_ptr<void> state;
};
而且鲍勃是你的叔叔。
上面可能有错别字。它也不支持将任意障碍物移入其中;添加一个构造函数应该会使这变得容易。
所有对arrive
的调用都涉及内存分配,而所有对wait
的调用都涉及到释放内存,因为不知道arrival_token
是什么;理论上,我们可以创建一个栈上类型擦除的令牌,并限制其大小,如果我自己使用这个功能,我可能会这样做。
arrive_and_wait
和arrive_and_drop
不使用堆分配。如果您不需要它们,可以省略arrive
和wait
。
一切都通过手动虚表反弹,所以会有一些性能损失。您必须检查它们是否足够好。
我将这种技术称为C++值类型擦除,在其中以C式风格手动实现多态性,但使用C++模板自动化类型擦除生成代码。当我们在语言中拥有编译时反射时(敲木头),它将变得不那么丑陋,并且是您可能要执行的实现std::function
的操作。
如果您将到达令牌从一个屏障传递到另一个屏障,代码会以有趣的方式崩溃。但是std::barrier
也会这样做,所以这似乎是公平的。
一个完全不同的方法,实现方式有趣地相似,是编写一个nothrow-on-call
std::function
。
有效地实现
std::function
通常涉及类似于上面的
vtable
方法,以及小缓冲区优化(SBO),以避免对小函数对象进行内存分配(我提到想要使用
poly_arrival_token
)。
decltype
,从而允许您编写std::barrier<decltype([]() noexcept { /* logic */ })> barrier;
。 - Matt Cummins