IE盒子

搜索
查看: 126|回复: 4

《Effective C++》第三章笔记 资源管理

[复制链接]

2

主题

10

帖子

17

积分

新手上路

Rank: 1

积分
17
发表于 2023-1-11 22:11:10 | 显示全部楼层 |阅读模式
最近在温习这本经典的Effective C++,好记性不如烂笔头,记录要点,如有不正确之处,欢迎批评指正。
第一章笔记链接:《Effective C++》第一章笔记 让自己习惯C++
第二章笔记链接:《Effective C++》第二章笔记 构造/析构/赋值运算
第三章笔记链接:《Effective C++》第三章笔记 资源管理
第四章笔记链接:《Effective C++》第四章笔记 设计与声明 [待更新]
第五章笔记链接:《Effective C++》第五章笔记 实现 [待更新]
第六章笔记链接:《Effective C++》第六章笔记 继承与面向对象 [待更新]
第七章笔记链接:《Effective C++》第七章笔记 模板与泛型编程 [待更新]
第八章笔记链接:《Effective C++》第八章笔记 定制new和delete [待更新]
<hr/>引言

所谓资源,一旦向系统申请,将来就得还给系统。资源有内存、网络socket、互斥锁mutex等,如果管理不好就可能导致内存泄漏、coredump等糟糕的结果。
本章介绍了RAII、智能指针、资源复制、new和delete等关于资源使用的知识,遵循本章的建议,可以几乎消除资源管理的问题,是每个C++程序员都得掌握的基础。
条款13:以对象管理资源

首先说说内存泄漏这个词,之前我不理解为什么要叫“泄漏”,后来明白了:当有一块分配的内存不能被系统回收,那么对于系统来说这块内存就是“泄漏”了,好比气球漏气,对气球而言有一些“气资源”泄漏了。
本条例建议用对象来管理资源,举个例子:
Column* createColumn(); // 创建一列,返回一个指向创建的列的指针,调用者需要手动删除
调用处:
void useColumn(){
  Column* ptr = createColumn();
  ...
  delete ptr; // 释放对象
}其实这样风险很大:如果在省略处(...)提前return,就发生了资源泄漏。虽然可以小心的避免,但心智成本变高了,软件的维护代价也变大了
上述代码可以修改为用智能指针管理资源(本质也是用对象管理资源),不用操心资源释放的事,出作用域有析构函数自动帮你完成:
void useColumn(){
  std::auto_ptr<Column> ptr(createColumn());
}以对象管理资源有两个关键想法:

  • 获得资源应该立刻放进管理对象(manager object)内
  • 管理对象(manager object)应用析构函数确保资源被释放
这也称为RAII(Resource Acquisition Is Initialization):资源获取时机便是初始化时机
本条款也提到了auto_ptr一个坑爹的地方:
auto_ptr采用copy语义来转移指针资源,转移指针资源的所有权的同时将原指针置为NULL
比如:
std::auto_ptr<Column> p1(createColumn());

std::auto_ptr<Column> p2 = p1; // p2指向对象,p1 = null

p1 = p2; // p1指向对象,p2 = null新指针如果是通过copy构造函数或者copy assignment操作符复制老指针得到的,老指针会变成nullptr
上面代码使用auto_ptr会让被指向物为null,使用shared_ptr则不会
听组里的老师傅也说过不建议使用auto_ptr,详细内容查看: auto_ptr的缺陷在哪里?为什么不应该用?
手动给互斥量加锁解锁添麻烦了,心智成本高,你可能希望建立一个class来管理加解锁,下述是用互斥量mutex+RAII实现的自动析构解锁类Lock,更详尽的版本可以参考lock_guard源码(很简短)
class Lock{
public:
  Lock(){ mx.lock(); }
  ~Lock(){ mx.unlock(); }
}
private:
    std::mutex mx; // mutex不允许拷贝构造和拷贝操作,最初产生的mx是未加锁状态
};条款14:在资源管理类中小心coping行为

继续考虑上述Lock示例,当某个使用RAII对象被复制会怎么样?对于mutex来说是禁止拷贝的,只能传参引用,因为如果允许拷贝,可能存在多个线程访问互斥区。
如果从特殊到一般情况呢,当某个使用RAII对象被复制会怎么样?有哪些可能?

  • 禁止复制。如上述Lock
  • 使用引用计数法。这种情况系复制RAII对象时,应该将它的引用计数增加1,当引用计数为0时才删除,可以采用shared_ptr
class Lock{
public:
  explict Lock(mutex& mx_):mx(mx_,unlock){}

private:
  std::shared_ptr<mutex> mx;
};

  • 复制底部资源。deep copy
  • 转移底部资源的拥有权。某些场景你希望只有一个RAII对象指向一个raw resource,即使RAII对象复制也是这样,类似unique_ptr或者auto_ptr
本条款主体内容到此就结束了,主要说了RAII对象的复制,小结:

  • 复制RAII对象需要一并复制它所管理的资源,资源的coping行为决定RAII对象的coping行为
  • 普遍的RAII class coping行为有:禁止复制、施行引用计数法
条款15:在资源管理类中提供对原始资源的访问

创建一个shared_ptr指针
std::shared_ptr<Column> pCol(createColumn());将指针指针作为某函数的参数
int getRows(const Column* ptr);  //返回列的行数
int rows = getRows(pCol); // 错误错误原因是getRows需要的是Column*指针,而传入的却是智能指针
因此,需要取得RAII对象内的原始资源,可以用智能指针的get方法
int rows = getRows(pCol.get()); // 正确
获取原始资源有两种方式,显示转化和隐式转化

  • 显示转化:如提供get()接口
  • 隐式转化:增加一个名为类名的重载函数,返回原始资源
代码示例:
class A {
  A get() const { return a; } // 显示转化

  // 隐式转化
  operator A() const {
    return a;
  }

  // 原始资源
  ... a ...
};条款16:成对使用new和delete时要采取相同形式


  • new的作用:1.分配一块内存,2.调用一个或多个构造函数
  • delete的作用:1.调用一个或多个析构函数 2.释放内存
new和delete的都需要确认构造和析构元素的个数,而这个参数由[]提示编译器来计算得到。
举个例子:
std::string* strP1 =  new std::string; //申请1份string的内存并构造
std::string* strP2 = new std::string[100]; // 申请100份string的内存并构造
delete strP1; // 删除一个string对象
delete[] strP2; // 删除多个(100个)string对象如果出现new的delete不匹配的情况,程序将发生程序结果未定义。
这一条款的内容可以用一句话概况:

  • 如果你在new表达式中用了[],必须在相应delete表达式中也用[]
  • 如果你的new表达式不包含[],那么相应的delete表达式也一定不要包含[]
条款17:以独立语句将newed对象置入智能指针

这一条款起提示的作用,实际情况会踩坑的概率较小。
举个例子:
写一个函数,一个参数传入列的智能指针,另一个参数传入列的优先级,当该函数被多个线程调用时,按照优先级执行。
processColumn(std::shared_ptr<Column> pCol, int priority() );现调用它
processColumn(std::shared_ptr<Column>(new Column), getPriority() );而上述调用可能导致资源泄漏
第一个参数std::shared_ptr<Column>(new Column)会做两件事情:

  • 调用new Column
  • 调用std::shared_ptr构造函数
对第二个参数的调用getPriority()可能排在第一或第二或第三执行(并不确定),如果编译器选择将它作为第二顺序,则会得到这样的操作序列

  • 调用new Column
  • 调用getPriority()
  • 调用std::shared_ptr构造函数
那么,如果getPriority()函数中出现异常,会导致new Column得到的指针不会被正确用给std::shared_ptr初始化,会引发资源泄漏。
避免这类问题很简单,将new抽离成独立语句即可解决。
std::shared_ptr<Column>  pCol(new Column)

processColumn(pCol, getPriority() );

  • 以独立语句将newed的对象存储于智能指针内,不过不这样,一旦异常被抛出,有可能导致难以察觉的资源泄漏。
(第三章完)
回复

使用道具 举报

1

主题

9

帖子

14

积分

新手上路

Rank: 1

积分
14
发表于 2023-1-11 22:12:01 | 显示全部楼层
某一个cppcon里提过scope guard非常实用
回复

使用道具 举报

1

主题

7

帖子

11

积分

新手上路

Rank: 1

积分
11
发表于 2023-1-11 22:12:54 | 显示全部楼层
追更~~催更~[大笑]
回复

使用道具 举报

4

主题

7

帖子

15

积分

新手上路

Rank: 1

积分
15
发表于 2023-1-11 22:13:43 | 显示全部楼层
[谢邀][谢邀]
回复

使用道具 举报

2

主题

9

帖子

9

积分

新手上路

Rank: 1

积分
9
发表于 2023-1-11 22:13:56 | 显示全部楼层
[赞同]
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表