如何将std::barrier存储为类成员,因为完成函数可以是lambda,所以你不能正确地输入它,并且使用std::function< void(void) noexcept >也不起作用,因为std::function似乎不支持noexcept关键字。因此,std::barrier完成函数似乎没有泛型基类型小例子
std::barrier
std::function< void(void) noexcept >
std::function
noexcept
#include <barrier> struct Storage { std::barrier< ?? > barrier; }
字符串
enyaitl31#
你可以把一个类型擦除的CompletionFunction放到std::barrier中,即使它没有自适应构造函数。它可能需要额外的转换。由于std::barrier对其CompletionFunction的各种要求,您可能必须使用仅移动类型的擦除函数。注意std::barrier不支持赋值-使用smart-storage(unique ptr似乎是最好的)可能会解决你在这里遇到的任何问题。无论你使用哪种 Package 结构, Package 结构的语义都会变得复杂。注意复制一个屏障在语义上是无意义的;移动一个屏障可能是有意义的。
CompletionFunction
std::barrier不支持多态的CompletionFunction类型/值--它没有其他类型中的适配构造函数。因此,在中使用std::function是不可行的,不管它支持什么--两个具有不同CompletionFunction类型的std::barrier是不相关的,不能相互赋值。你可以自己输入erase,然后写一个poly_barrier。这样做的障碍(双关语)是arrival_token似乎不支持多态性;不能保证在不同的std::barrier<X>情况下它是相同的类型。它是MoveConstructible,MoveAssignable和Destructible,所以我们也可以输入erase it。第一个API草图:
poly_barrier
arrival_token
std::barrier<X>
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; };
字符串我们现在写一个vtable:
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; };
型鲍勃是你叔叔上面可能有错别字。它也不支持移动任意的障碍;添加一个ctor应该会很容易。所有对arrive的调用都涉及内存分配,所有对wait的调用都涉及内存释放,因为不知道arrival_token是什么;理论上,我们可以创建一个大小有限的堆栈类型擦除令牌,如果我自己使用这个令牌,我可能会这样做。arrive_and_wait和arrive_and_drop不使用堆分配。如果不需要,可以删除arrive和wait。所有的东西都是通过手动vtable跳转的,所以会有一些性能问题,你必须检查它们是否足够好。我知道这种技术是C++ value-style type erasure,我们用C风格手动实现多态性,但是我们使用C++模板自动生成类型擦除代码。当我们在语言中有编译时反射时,它会变得不那么丑陋(敲木头),并且是你可能会做的事情来实现std::function。如果你将到达令牌从一个障碍传递到另一个障碍,代码会以有趣的方式爆炸,但std::barrier也是如此,所以这似乎是公平的。一种完全不同的方法,具有有趣的相似实现,是编写一个nothrow-on-call std::function。有效地实现std::function通常涉及到类似于上面的vtable方法的东西,以及小缓冲区优化(SBO),以避免使用小函数对象分配内存(我提到希望使用poly_arrival_token)。
arrive
wait
arrive_and_wait
arrive_and_drop
vtable
poly_arrival_token
00jrzges2#
下面是一个C++17中的工作示例,使用std::barrier对象作为类成员变量,沿着arrive_and_wait方法同步多个线程:
#include <barrier> #include <functional> #include <thread> #include <vector> using namespace std; class Foo{ public: explicit Foo(int thCnt) : b(thCnt,[](){}){} barrier<void(*)()> b; //barrier<function<void()>> b; //This works also void thFun(int thNum){ printf("thNum: %i\n",thNum); b.arrive_and_wait(); } }; int main(){ int numWorkers = 5; Foo f(numWorkers + 1); // +1 for the b.arrive_and_wait called in main() vector<thread> threadVec(numWorkers); for(int i = 0 ; i<numWorkers; ++i){ threadVec.emplace_back(&Foo::thFun,&f,i); } f.b.arrive_and_wait(); //barrier waits for all threads to finish. for(auto &th : threadVec){ if(th.joinable()) th.join(); } printf("done\n"); return 0; }
2条答案
按热度按时间enyaitl31#
警告:此答案包含错误
你可以把一个类型擦除的
CompletionFunction
放到std::barrier
中,即使它没有自适应构造函数。它可能需要额外的转换。由于
std::barrier
对其CompletionFunction
的各种要求,您可能必须使用仅移动类型的擦除函数。注意
std::barrier
不支持赋值-使用smart-storage(unique ptr似乎是最好的)可能会解决你在这里遇到的任何问题。无论你使用哪种 Package 结构, Package 结构的语义都会变得复杂。注意复制一个屏障在语义上是无意义的;移动一个屏障可能是有意义的。原始的,过于复杂的答案:
std::barrier
不支持多态的CompletionFunction
类型/值--它没有其他类型中的适配构造函数。因此,在中使用std::function
是不可行的,不管它支持什么--两个具有不同CompletionFunction
类型的std::barrier
是不相关的,不能相互赋值。你可以自己输入erase,然后写一个
poly_barrier
。这样做的障碍(双关语)是arrival_token
似乎不支持多态性;不能保证在不同的std::barrier<X>
情况下它是相同的类型。它是MoveConstructible,MoveAssignable和Destructible,所以我们也可以输入erase it。
第一个API草图:
字符串
我们现在写一个vtable:
型
然后我们用途:
型
鲍勃是你叔叔
上面可能有错别字。它也不支持移动任意的障碍;添加一个ctor应该会很容易。
所有对
arrive
的调用都涉及内存分配,所有对wait
的调用都涉及内存释放,因为不知道arrival_token
是什么;理论上,我们可以创建一个大小有限的堆栈类型擦除令牌,如果我自己使用这个令牌,我可能会这样做。arrive_and_wait
和arrive_and_drop
不使用堆分配。如果不需要,可以删除arrive
和wait
。所有的东西都是通过手动vtable跳转的,所以会有一些性能问题,你必须检查它们是否足够好。
我知道这种技术是C++ value-style type erasure,我们用C风格手动实现多态性,但是我们使用C++模板自动生成类型擦除代码。当我们在语言中有编译时反射时,它会变得不那么丑陋(敲木头),并且是你可能会做的事情来实现
std::function
。如果你将到达令牌从一个障碍传递到另一个障碍,代码会以有趣的方式爆炸,但
std::barrier
也是如此,所以这似乎是公平的。一种完全不同的方法,具有有趣的相似实现,是编写一个nothrow-on-call
std::function
。有效地实现
std::function
通常涉及到类似于上面的vtable
方法的东西,以及小缓冲区优化(SBO),以避免使用小函数对象分配内存(我提到希望使用poly_arrival_token
)。00jrzges2#
下面是一个C++17中的工作示例,使用
std::barrier
对象作为类成员变量,沿着arrive_and_wait
方法同步多个线程:字符串