经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C++ » 查看文章
C++ 核心指南之资源管理(中)
来源:cnblogs  作者:Zijian/TENG  时间:2023/6/26 8:50:20  对本文有异议

C++ 核心指南(C++ Core Guidelines)是由 Bjarne Stroustrup、Herb Sutter 等顶尖 C++ 专家创建的一份 C++ 指南、规则及最佳实践。旨在帮助大家正确、高效地使用“现代 C++”。

这份指南侧重于接口、资源管理、内存管理、并发等 High-level 主题。遵循这些规则可以最大程度地保证静态类型安全,避免资源泄露及常见的错误,使得程序运行得更快、更好。

R.alloc: 分配和释放

  • R.10: 避免使用 malloc() / free()
  • R.11: 避免显式调用 new / delete
  • R.12: 显式资源分配的结果应立即给到资源管理对象
  • R.13: 在一条语句中,最多只能有一个显式资源分配
  • R.14: 避免使用 [] 参数,用 span 替代
  • R.15: 分配/释放操作要成对重载

R.10: 避免使用 malloc() / free()

malloc() / free() 不支持构造、析构,不要和 new / delete 混用。

例子

  1. class Record {
  2. int id;
  3. string name;
  4. };
  5. void use()
  6. {
  7. // p1 可能是 nullptr;*p1 未初始化,尤其是其中的 name 不是一个合法的 string 对象
  8. Record* p1 = static_cast<Record*>(malloc(sizeof(Record)));
  9. // 除非抛异常,*p2 默认初始化
  10. auto p2 = new Record;
  11. // p3 可能是 nullptr;如果不为空,*p3 默认初始化
  12. auto p3 = new(nothrow) Record;
  13. delete p1; // error: 不能 delete 由 malloc() 返回的指针
  14. free(p2); // error: 不能 free() new 出来的对象
  15. }

最后的 delete、free 在有的实现中可能正常工作,有的会导致运行时错误。

例外

有的应用中禁止异常,如 life-critical 和硬实时系统。但是很多针对异常的禁用只是迷信,或是担心导致旧代码资源管理上的混乱。如果是这种情况,可以考虑 nothrow 版本的 new

代码检查建议

标记显式的 malloc/free 调用

R.11: 避免显式调用 new / delete

new 返回的指针应该属于资源句柄(在资源句柄的析构中自动调用 delete)。如果 new 返回值赋给了裸指针,可能导致资源泄露。

在大型项目中,如果在应用代码中(而不是在专门资源管理类中)出现 delete,那多半会有 bug:如果代码里有几处 delete 调用,你怎么保证没有多调用或者少调用?这类 bug 不一定能立即发现,可能在潜伏一段时间后,在某次代码维护/重构时暴露。

代码检查建议

针对显式的 new / delete 给出警告,建议使用 make_unique 替代

R.12: 显式资源分配的结果应立即给到资源管理对象

否则,一旦抛异常或返回将导致资源泄露

反面例子

  1. void func(const string& name)
  2. {
  3. // 打开文件
  4. FILE* f = fopen(name, "r");
  5. vector<char> buf(1024);
  6. // 关闭文件
  7. auto _ = finally([f] { fclose(f); });
  8. // ...
  9. }

buf 分配空间可能失败抛异常,导致 f 文件句柄泄露

正面例子

  1. void func(const string& name)
  2. {
  3. ifstream f{name};
  4. vector<char> buf(1024);
  5. // ...
  6. }

文件句柄在 ifstream 内部,ifstream 销毁时自动 fclose 文件句柄,简单、安全、高效。

代码检查建议

标记那些用来初始化指针的显式资源分配

R.13: 在一条语句中,最多只能有一个显式资源分配

如果在一条语句中执行两个显式资源分配,可能导致资源泄露。因为很多子表达式的求值顺序(包括函数参数)是未定义的。

例子

  1. void fun(shared_ptr<Widget> sp1, shared_ptr<Widget> sp2);

如果像下面这样调用 fun()

  1. // BAD: 可能泄露
  2. fun(shared_ptr<Widget>(new Widget(a, b)), shared_ptr<Widget>(new Widget(c, d)));

上述调用是“异常不安全”(exception-unsafe)的,因为编译器可能会对创建两个参数的表达式重新排序。特别是编译器可能交叉执行两个子表达式:先给 sp1、sp2 分配内存空间、然后调用 Widget 的构造。如果此时在构造某一个参数的时候抛出异常,则另一个对象的内存不会被释放!

解决这个问题也很简答,不在一条语句里出现多个显式资源分配即可。例如;

  1. // 稍好,但有点乱
  2. shared_ptr<Widget> sp1(new Widget(a, b));
  3. fun(sp1, new Widget(c, d));

最好的办法是完全避免显式资源分配,而是通过工厂函数返回拥有的对象:

  1. // 最佳实践
  2. fun(make_shared<Widget>(a, b), make_shared<Widget>(c, d));

如果没有像 make_shared、make_unique 这样的工厂函数,自己封装一个。

代码检查建议

如果一条语句内有多个显式资源分配,标记该语句

R.14: 避免使用 [] 参数,用 span 替代

数组形参退化为指针,丢失数组大小信息,容易导致边界错误。用 span 可以保留数组大小信息。

例子

  1. // 不推荐
  2. void f(int[]);
  3. // 不推荐指针指向多个对象
  4. // 指针应该指向单个对象(见 R.2)
  5. void f(int*);
  6. // 推荐
  7. void f(gsl::span<int>);

R.15: 分配/释放操作要成对重载

否则将导致混乱

例子

  1. class X {
  2. void* operator new(size_t s);
  3. void operator delete(void*);
  4. };

如果希望内存不被释放,用 =delete 明确禁止释放操作。

代码检查建议

标记不成对的分配/释放操作

原文链接:https://www.cnblogs.com/tengzijian/p/17503953.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号