CppCoreGuidelines I.30 把违背规则的操作封装隔离起来
03 April 2022
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; };
这样,就封装了输入流的所有权。实际代码中再增加一些错误检测即可。