【问题标题】:RAII equivalent for FIFO release orderFIFO 释放订单的 RAII 等效项
【发布时间】:2020-06-09 21:55:55
【问题描述】:

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

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

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
}

也就是说,我们将在释放even 插槽之前释放odd 插槽。虽然我可以在stream 中添加一个“重新排序”队列,但我想知道是否有一种“干净”的方式将 RAII 推广到这些情况。

【问题讨论】:

  • 不要让调用者直接访问T,给他们一个shared_ptr&lt;Node&lt;T&gt;&gt;,每个节点都有一个shared_ptr指向列表中的下一个节点。然后,当第二个指针被销毁时,Node 将保持 T 处于活动状态,直到第一个指针也被销毁。哦等等,实际的破坏仍然以错误的顺序发生。该死。
  • 不能使用std::queue 吗?从我所看到的情况来看,它会按照自身销毁时输入的顺序销毁所包含的元素。
  • 这很微妙,但我认为您的类型违反了“资源”的隐含合同。因此,您不应该将其视为资源(相反,考虑将整个事务包装到它自己的资源中,这实际上是建议使用队列的评论和答案正在做的事情)。跨度>
  • 匿名者:你有没有尝试过你在答案中得到的任何建议?
  • @TedLyngmo 我做到了,您基于队列的解决方案很好(从可读性的角度来看),但我的情况是嵌入式硬件,因此动态分配很昂贵/不可用。

标签: c++ raii


【解决方案1】:

使用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 第二个被销毁。相同的有效构造和销毁顺序。

【讨论】:

    【解决方案2】:

    这是一个使用 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引用的元素。

    【讨论】:

    • 我接受了你的回答,因为这对我来说是最干净的。但实际上它不适用于我的用例,因为我在嵌入式设备上(动态分配太昂贵或不可用)。
    • @Nonyme 嗯,这太糟糕了,它并没有真正帮助你。队列中的类只包含两个引用,所以我希望它在系统上相当容易。也许您应该选择 Sam 当时展示的 std::optional 版本?
    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-07-26
      • 2015-07-06
      • 2017-08-02
      • 1970-01-01
      • 1970-01-01
      • 2018-11-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多