条例13~17(资源管理)

条例13

以对象管理资源

  • 有的时候,我们主动控制去调用delete函数是不可靠的,有可能提前返回或者遇到异常跳过了delete。为了保证资源正确释放。我们可以通过让对象管理资源。把释放操作放在另一个对象的析构函数内。析构函数的自动调用机制节能确保对象正常释放。
  • 标准库提供了只能指针来解决这个问题。auto_ptr,unique_ptr,shared_ptr。智能指针获得资源后立刻初始化,同时通过析构函数调用资源释放。auto_ptr不能指向相同的对象,不然会多次释放。同时auto_ptr的拷贝构造和拷贝赋值是浅拷贝,直接交换null和原来的值,复制的指针是资源管理的唯一控制者。这种行为非常怪异。
  • 使用shared_ptr也就是引用计数型智能指针可以做到正常拷贝,两个智能指针管理一个资源。但注意不能解决环状引用的问题(引入weak_ptr可以解决环状引用的问题)
  • 若必须要手动控制资源的释放,就需要手动生成资源管理类。

总结

  • 为了防止资源泄漏,需要使用raii对象,他们在构造函数中获得资源,并在析构函数中释放资源
  • 常用的智能指针是shared和auto,前者效果更佳。

条例14

在资源管理类中小心copying行为

  • 有时我们不单单希望用raii控制资源的获取和释放,还可以用这样的观念管理锁。因为加锁解锁也不能被跳过。我们同样可以通过一个资源管理类管理锁,构造函数加锁,析构函数解锁。
  • 自己写资源管理类会遇到一个问题,针对拷贝操作的处理,有两种解决方案,一是禁止复制,二是引入引用计数。当引用计数为0是销毁资源,但这里我们想要的是解锁操作。我们可以通过手动指定删除器,将销毁资源的操作替换为解锁。(删除器与要在构造函数内传入)手动声明删除器后就不用在声明析构函数了,编译会根据删除器自动生成。

总结

  • 复制raii对象必须一并复制它所管理的资源,所以资源的拷贝行为决定了raii的拷贝行为。
  • 常见的raii行为禁止拷贝,实行引用计数。

条例15

在资源管理类中提供对原始资源的访问

  • 有的时候我们使用了智能指针,但你却想调用智能指针包裹的原生指针。这时候我们需要能将智能指针转换为原声指针的功能。有两种解决方法,显示调用和隐式调用
  • 智能指针内部都提供了get函数用于获取原生指针,可以用于显示调用,同时也重载了*和->操作符,用来操作原生对象的成员,以用于隐式类型转换。
  • 当你自己实现了RAII,且不想通过get函数频繁获取原生对象,还可以手动在RAII对象内声明隐式类型转换函数(例如 operator 转换目标类型()const)。以便编译器自动调用。但使用这种隐式类型转换会遇到问题。在复制对象的时候若声明了错误的类型名称,就有可能拿到本应该由RAII管理的原生对象,这样当该原生被销毁的时候就有可能产生悬空的对象,不是很安全。所以通常使用get函数显示调用好一些。

总结

  • 每个RAII对象都应该提供一个取得其管理资源的方法。
  • 对原始资源的调用可以通过显示调用或者隐式调用。通常显示调用更加安全。

条例16

成对使用new和delete时要采取相同的形式

  • 若使用new 就要用delete释放,若使用new[]就要使用delete[]释放,不让类型不匹配会出现问题。有可能会导致析构函数没有被调用
  • 当使用new的时候,实际执行两件事,首先调用operator new 开辟出内存(等同于malloc),在调用针对此内存调用构造函数。 delete操作就是这两步操作反过来执行。对于delete而言他需要知道被删除内存内有多少个对象,他需要调用多少次析构函数。可以从内存布局来理解,单一对象就一块空间不用特殊处理,调用一次析构函数。对于多个对象,会在对象数组前面多放上一个字节来描述当前内存有几个对象,释放的时候delete通过这个字节获取到要执行几个析构,并向后偏移找到内存真正的起始地址。在调用对应次数的析构含函数。由此可见new和delete类型不匹配势必会发生错误。
  • 这点在使用typedef的时候尤其重要,若是将数组对象typedef了就有可能出现歧义。

总结

若使用new[]则必须使用delete[],反之同理。

条例17

以独立语句将new出来的对象放入智能指针

  • 当我们的在函数传参的同一句语句里调用new的时候,哪怕使用了RAII也有可能面临资源泄漏。因为c++里函数参数的执行顺序是不确定的,假如这个函数还有一个参数我们叫他参数2,这个参数依赖于将要new出来的对象。此时编译器的执行顺序可能是先new了一个对象,然后执行参数2,最后执行RAII。但若这时候,执行参数2的时候抛出了异常,就会出现资源泄漏。因为此时RAII还没有被创建。
  • 针对这种情况,我们最好把new出来的对象封装进RAII单独写成一句语句。虽然同一句语句内的执行顺序不确定,但是上下句的执行顺序是确定的。

总结

以独立语句封装new出来的对象储存在智能指针内,若不这样做,一旦异常抛出,有可能资源泄漏。

你可能感兴趣的:(Effective,c++,c++)