点击上方“C语言与CPP编程”,选择“关注/置顶/星标公众号”
干货福利,第一时间送达!
最近有小伙伴说没有收到当天的文章推送,这是因为微信改了推送机制,确实会一部分有小伙伴刷不到当天的文章,一些比较实用的知识和信息,错过了就是错过了。所以建议大家加个星标⭐️,就能第一时间收到推送。
小伙伴们大家好,我是飞宇。
今天继续更新《Effective C++》和《C++并发编程实战》的读书笔记,下面是已经更新过的内容:
《C++并发编程实战》读书笔记(1):并发、线程管控
《C++并发编程实战》读书笔记(2):并发操作的同步
《Effective C++》读书笔记(2):构造/析构/赋值运算
《Effective C++》读书笔记(1):让自己习惯C++
所谓资源就是,一旦用了它,将来必须还给系统,包括最常使用的动态分配内存、文件描述符、互斥锁等等。由于异常、函数内多重回传路径、版本更改时遗漏等原因,任何时候都确保这一点是很难的。
本章总结了基于对象的一般化资源管理办法以及一些专属条款。严守这些做法几乎可以消除资源管理问题。
条款13、以对象管理资源
假设你有一个工厂函数用来获取一个动态分配对象,那么任何调用它的用户有责任删除这个对象:
Widget* create_Widget() { ... }
void some_function(){
Widget* p_Widget = create_Widget();
...
delete p_Widget;
}
上述some_function函数看似没有问题,实际上仍有很多情况会导致不会执行删除操作,例如某个过早的return、某个异常、多个版本后维护人员遗忘了这一点等等。
为了确保动态获取的资源一定会被释放,可以用对象来管理资源,将获取资源的行为放在构造函数中,将释放资源的行为放在析构函数中;那么,不论程序如何运行,一定会执行析构函数,一定会释放资源。这种做法称为RAII(Resource Acquisition Is Initialization,资源获取即初始化)。
标准库中的智能指针可以辅助管理资源,其中shared_ptr的资源可以共享,通过引用计数来控制行为,引用计数归零时删除资源,而unique_ptr独享资源。更多的使用与实现可以查阅cppreference。
void some_function(){
//资源获取即初始化
unique_ptr
p_Widget(create_Widget()); ...
//退出局部作用域,unique_ptr调用Widget的析构函数
}
条款14、在资源管理类中小心coping行为
对于管理堆对象来说,上文的智能指针已经足够。但是很多资源并非基于堆,需要自己实现一个RAII类来管理,这时就需要考虑一个问题:怎么处理拷贝? 通常有两种思路:
1、禁止拷贝。很多资源被复制是不合理的,因此可以用条款6中的方法来禁止拷贝构造/拷贝运算符。
2、对底层资源使用引用计数法。有时我们希望保有资源直到最后一个用户使用完,这时就可以用shared_ptr代替裸指针来管理底层资源,用shared_ptr的删除器来控制资源的析构行为。
假设需要包装Mutex互斥器,目前只有lock、unlock两个函数。因为资源通过这两个函数来获取与释放,不是通过堆,所以需要自己实现RAII类。
void lock(Mutex* pm);
void unlock(Mutex* pm);
class Lock{
public:
explicit Lock(Mutex* pm):sp_mutex(pm,unlock){
lock(sp_mutex.get());
}
private:
std::shared_ptr
sp_mutex; };
不过也有一些其他做法,例如将拷贝操作实现为深拷贝、将拷贝操作实现为转移资源的拥有权等。
条款15、在资源管理类中提供对原始资源的访问
各类API往往要求访问原始资源,只提供了裸指针的接口,因此对于RAII类来说也应该提供一个“取得其所管理之资源”的方法。 或许有些破坏了类的封装性质,但对于RAII类来说问题不大,因为根本上来说它只是为了管理资源的获取与释放。
至于如何访问原始资源,一般分为显式转换与隐式转换。
1、显式转换,例如shared_ptr的get函数。因为需要明确指定,所以比隐式转换更安全。
2、隐式转换,用法更自然,但可能出错
class WidgetHandle {};
class Widget {
public:
operator WidgetHandle() const { return w; }
private:
WidgetHandle w;
};
void f(WidgetHandle w) { ... }
int main() {
Widget w;
f(w);
}
条款16、成对使用new和delete时要采取相同形式
当删除指针时,为了让delete知道要处理的是单个对象还是数组,如果new表达式使用[]则delete表达式也应使用,如果new没有使用则delete也不应使用。
此外,为了避免失误,最好不要为数组形式进行typedef/using。事实上,STL中的vector、array基本可以替代原生数组。
条款17、以独立语句将newed对象置入智能指针
在函数传参时new一个指针再初始化智能指针是不安全的:
some_function(std::shared_ptr(new Widget), f());
考虑上面这个函数调用,假设f()出现异常,则前面申请的内存将没处释放了。这种内存泄露的本质是当申请数据指针后,没有马上传给std::shared_ptr。
解决方法有两个:1、在函数调用前先用独立语句初始化shared_ptr,再传给函数。2、函数传参时使用make_shared来初始化智能指针,它只执行一次内存申请,更加异常安全。
std::shared_ptr
pw(new Widget); some_function(pw,f());
some_function(make_shared
(),f());
—— EOF —— 你好,我是飞宇,本硕均于某中流985 CS就读,先后于百度搜索以及字节跳动电商等部门担任Linux C/C++后端研发工程师。
同时,我也是知乎博主@韩飞宇,日常分享C/C++、计算机学习经验、工作体会,欢迎点击此处查看我以前的学习笔记&经验&分享的资源。
我组建了一些社群一起交流,群里有大牛也有小白,如果你有意可以一起进群交流。
欢迎你添加我的微信,我拉你进技术交流群。此外,我也会经常在微信上分享一些计算机学习经验以及工作体验,还有一些内推机会。
加个微信,打开另一扇窗