首页 > 解决方案 > 回调的这种用法是惯用的吗?

问题描述

我注意到我的一些代码中有一个共同的模式

std::string line;
if (!in || !std::getline(in, line)) {
  throw line_read_error(in,line_counter);
}
++line_counter;
std::istringstream sin{line};

// ...read from sin...

if (!sin.eof()) {
  sin.clear();
  throw incomplete_read_error(in,line_counter,sin.tellg());j
}

我从该行读取的内容在每个位置都不同,但设置和读取后检查是相同的。

我把它分解出来,创建一个对象来保存我的in流和line_counter,并为主体传递一个回调:

class LineReader {
  std::istream& in;
  size_t line_counter;
public:
  template <typename Function>
  void with_next_line(Function callback) {
    std::string line;
    if (!in || !std::getline(in, line)) {
      throw line_read_error(in,line_counter);
    }
    ++line_counter;
    std::istringstream sin{line};

    callback(sin);

    if (!sin.eof()) {
      sin.clear();
      throw incomplete_read_error(in,line_counter,sin.tellg());j
    }
  }
  // ...
}

将我的用途更改为

line_reader.with_next_line([/*...*/](auto& sin){
  // ...read from sin...
});

这肯定是少了重复,但还是有点尴尬。

然而,我真正关心的是其他人是否容易理解,因为我真的在努力使我的代码尽可能清晰。

像这样的东西我会更好吗

auto sin = line_reader.get_next_line();

// ...read from sin...

line_reader.check_done(std::move(sin));

标签: c++callbackidioms

解决方案


进行设置 + 清理 的正常方法是拥有一个对象,其构造函数执行设置,析构函数执行清理 (RAII)。

但是,如果您还没有读到最后,您想要做的清理就是抛出 - 从析构函数中抛出是邪​​恶的、坏的和错误的。这意味着在这种特殊情况下您不能使用 RAII。

如果进行检查真的很重要,那么您拥有的代码将强制执行它。如果这只是“一个好主意”,那么我认为这两个调用(之前和之后)可能比 lambda 稍微干净一些。(我不会打扰std::move(sin)- 使用移动并不会在这里添加任何东西。)


推荐阅读