相当于 FIFO 释放顺序的 RAII

RAII equivalent for FIFO release order

RAII 非常舒适,我很难为资源提供等效设计,这些资源必须按照获取它们的顺序(FIFO)释放,而不是按照自然产生的相反顺序(堆栈)释放RAII.

在我的具体情况下,我有一个 stream class 如下:

template<typename T>
class stream {
  ...
public:
  // Producer API
  T& write_acquire(); // This acquires a storage element and will "block"
                      // until a slot is available in the underlying resource
  void write_release(&T); // This releases the storage element, transferring the data to a consumer

  // Consumer API
  T& read_acquire(); // This acquires a storage element and will "block"
                     // until a slot has been write_release
  void read_release(&T); // This releases the storage element making it available
                         // for a potential future write_acquire
};

我正在考虑提供一个 RAII 风格的助手:

template<typename T>
class stream_wslot {
  stream<T> &s;
  T &slot;
public:
  stream_wslot(stream<T> &s) : s{s}, slot{s.write_acquire()} {}
  ~stream_wslot() { s.write_release(slot); }

  operator T&() { return slot; }
  T& operator=(T &val) { return slot = val; }
};

但问题是以下用法将无法正常运行:

void test(stream<float> &fifo) {
  stream_wslot even(fifo);
  stream_wslot odd(fifo);
  ... first ...
  ... second ...
  // releases odd !!!
  // releases even
}

即先释放odd槽,再释放even槽。虽然我可以在 stream 中添加一个 "reordering" 队列,但我想知道是否有一种 "clean" 方法可以将 RAII 推广到这些情况。

使用 std::optional,成本非常适中,最小的开销提供了对构建的更多控制,以及定义明确的销毁顺序。这正是您要找的,在这里。

例如:

std::optional<stream_wslot> o_even;
std::optional<stream_wslot> o_odd;

o_odd.emplace(fifo);
o_even.emplace(fifo);

auto &even=*o_even;
auto &odd=*o_odd;

从现在开始,使用 oddeven 的现有代码将很难区分。总计:odd 首先构建,even 第二构建。 odd 首先被销毁,当离开这个范围时, even 第二个被销毁。相同有效的构造和销毁顺序。

您可以在另一个线程上有一个队列 运行 以异步实现 FIFO 逻辑。如果 wslot 对象是使用对该队列的引用构造的,它们可以告诉它在它们析构时为它们执行 write_release(),但在构造时执行常规 write_acquire(),这将在必要时阻塞.

(或者,在构建时,他们可以在某处传递 std::future,并通过 write_release() 销毁来履行承诺。但这只是我的含糊其辞)。

这是一个使用 std::queue 并在 C++11 及更高版本中工作的替代方法:

#include <queue>

void test(stream<float> &fifo) {
    std::queue<stream_wslot> wslots;

  #if __cplusplus >= 201703L                             // C++17 and later:
    auto& even = wslots.emplace(fifo);
    auto& odd = wslots.emplace(fifo);
  #else                                                  // C++11 and C++14:
    auto& even = (wslots.emplace(fifo), wslots.front());    
    auto& odd = (wslots.emplace(fifo), wslots.front());
  #endif

    // work with even and odd ...
}

首先创建 even 引用的元素,然后创建 odd 引用的元素。

作用域结束时,std::queue先释放even引用的元素,然后释放odd引用的元素。