C++ std::barrier 作为类成员

5

如何将std::barrier存储为类成员变量?

由于完成函数可以是lambda表达式,因此无法正确地进行类型设置,而使用std::function< void(void) noexcept >也行不通,因为std::function似乎不支持noexcept关键字。

因此似乎并没有std::barrier完成函数的通用基础类型。

小例子:

#include <barrier>

struct Storage
{
    std::barrier< ?? > barrier;
}

1
对于lambda表达式,在C++20及以后的版本中,您可以在未求值的上下文中编写lambda表达式,例如decltype,从而允许您编写std::barrier<decltype([]() noexcept { /* logic */ })> barrier; - Matt Cummins
1
“因为完成函数可以是一个lambda表达式,但如果你不想使用lambda表达式,也可以使用命名类型,这并不会引起lambda警察的注意。这只是你自己制造的问题。” - Nicol Bolas
1个回答

0

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_waitarrive_and_drop不使用堆分配。如果您不需要它们,可以省略arrivewait

一切都通过手动虚表反弹,所以会有一些性能损失。您必须检查它们是否足够好。

我将这种技术称为C++值类型擦除,在其中以C式风格手动实现多态性,但使用C++模板自动化类型擦除生成代码。当我们在语言中拥有编译时反射时(敲木头),它将变得不那么丑陋,并且是您可能要执行的实现std::function的操作。

如果您将到达令牌从一个屏障传递到另一个屏障,代码会以有趣的方式崩溃。但是std::barrier也会这样做,所以这似乎是公平的。


一个完全不同的方法,实现方式有趣地相似,是编写一个nothrow-on-call std::function
有效地实现std::function通常涉及类似于上面的vtable方法,以及小缓冲区优化(SBO),以避免对小函数对象进行内存分配(我提到想要使用poly_arrival_token)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接