03 April 2022

C++ 核心指南目录

I.30: Encapsulate rule violations

理由

保持代码简洁、安全。但是有时候为了逻辑清晰和性能考量,需要用到一些丑陋、不安全、容易出错的技巧。如果用到了,那么将这些代码保持在本地,而不要影响到外部接口。这样使用外部接口的程序员就不需要关注内部细节。那些复杂的代码实现,尽可能不要通过接口流到用户代码中。

例子

考虑这样一个程序,通过文件、命令行、标准输入等不同的输入( main 函数的参数)执行。我们可能写成这样:

bool owned;
owner<istream*> inp;
switch (source) {
case std_in:        owned = false; inp = &cin;                       break;
case command_line:  owned = true;  inp = new istringstream{argv[2]}; break;
case file:          owned = true;  inp = new ifstream{argv[2]};      break;
}
istream& in = *inp;

以上代码违反了这些规则:

  • 不要有未初始化的变量
  • 不要忽略所有权
  • 不要用魔数常量

最后还得有人去清理销毁资源:

if (owned) delete inp;

以下,我们可以用一个有特殊删除操作的unique_ptr来实现,当然如果是 cin 就不需要删除。但这样对新手来说太麻烦。这里我们遇到的是一个静态变量,即所有权,偶尔需要在运行时检查。我们可以写一个类:

class Istream { [[gsl::suppress(lifetime)]]
public:
    enum Opt { from_line = 1 };
    Istream() { }
    // read from file
    Istream(zstring p) : owned{true}, inp{new ifstream{p}} {}
    // read from command line
    Istream(zstring p, Opt) : owned{true}, inp{new istringstream{p}} {}
    ~Istream() { if (owned) delete inp; }
    operator istream&() { return *inp; }
private:
    bool owned = false;
    istream* inp = &cin;
};

这样,就封装了输入流的所有权。实际代码中再增加一些错误检测即可。