以对象管理资源
checklists
函数直接返回 raw 指针极易造成资源泄漏,违背了“让接口容易被正确使用,不易被误用”的原则,替代可以选择返回RAII对象如 指针指针;
RAII是最常用的资源管理类对象,调用 delete 销毁指向的对象,对象的析构函数中不能抛出异常。
why
常见的计算机资源包含:动态分配的内存、文件描述器、互斥锁、图形界面中的字型和画刷、数据库连接及网络 sockets 等,使用后必须还给系统。
常规方式
class Investment {...}; // 投资类基类Investment* createInvestment( ); // 工厂方法,返回函数内动态创建的对象的指针,调用者需保证释放void f(){ Investment* pInv = createInvestment(); do{ if( nullptr == pInv ) { log.error( "..." ); break; } if( ERROR == dosomething( ) ){ log.error( "..." ); break; } ... }while(0) // for resource pInv if( pInv ) delete pInv; return ; }
借助于 do_while_0 实现类似于 go_to 的语法效果,已经极大改善了异常分支时资源的释放的代码结构,并且当上述结构遇到多个资源,会导致 do_while_0 的更加复杂的嵌套,灵活性很差。
对象管理资源的方式
shared_ptr
void f(){ std::shared_ptr<Investment> pInv( createInvestment() ); // 利用 shared_ptr 对象管理资源,脱离作用域,自动调用 delete pInv assert( nullptr != pInv.get() ); if( ERROR == dosomething( ) ){ log.error( "..." ); return; } return; }
获取资源后立刻放进管理对象,如上的 shared_ptr 对象,又被称为 Resource Acquisition Is Initialization; RAII;
管理对象利用析构函数来确保资源被释放,但是注意析构函数不能抛出异常,需要仔细处理资源的释放过程。
shared_ptr 释放资源时,调用 delete 函数而非 delete[]。
自定义资源管理对象
class Lock{public: explicit Lock( Mutex* pm) : mutexPtr( pm ) { lock( mutexPtr ); } ~Lock() { unlock( mutexPtr ); }private: Mutex* mutexPtr; };// usage exampleMutex m; ... { Lock m1(&m); Lock m2( m1 ); // ERROR, Lock 对象应禁止复制}
自定义管理对象的复制策略一般分为四种:
禁止复制策略
对底层资源采用引用计数,保有资源,直到最后一个使用者被销毁,此时才释放资源,复制时,引用计数增加。
复制底层资源,即对对象实行深拷贝;
转移底部对象的拥有权,如 move_construct 函数。
禁止复制策略
class Uncopyable{ protected: Uncopyable(){} // 1, 必须实现阻止系统默认构造函数; // 2, 外部不可见 // 3, 子类中可见 ~Uncopyable() {} private() {} Uncopyable( const Uncopyable&); // 1, 只有声明 // 2, 继承后称为子类的 private 成员函数,阻止自动生成的复制构造函数 Uncopyable& operator=( const Uncopyable& ); // 阻止默认拷贝赋值函数 };class Lock : private Uncopyable // 1, private 不改变可见性{ ... }
shared_ptr 指定释放方式
public:explicit Loak( Mutex* pm ) : mutexPtr( pm, unlock) { // 指定 unlock 为 shared_ptr 销毁时调用的资源释放方式 lock( pm ); }private:std::shared_ptr<Mutex> mutexPtr;
资源管理类需要提供对原始资源的访问
应尽可能的通过资源管理类操作资源,但是为了保证兼容老式 C 风格的接口, 如使用需要使用西永调用等API时,需要访问原始资源。
两种访问方式
1, 显示转换,如
shared_ptr<T>.get()
2, 隐式转换函数, 重载转换运算符operator T()
new 和 delete 必须对应
new 对应 delete, new [] 对应 delete[]
避免对数组 typedef , 防止 delete 与 new[] 不匹配问题
数组对象内存前有一个表示数组的个数, 调用 delete[] 会先读出数组的个数,然后对应的调用 n 次对象的析构,delete 直接释放第一个元素。
以独立语句初始化智能指针
processWidget( std::shared_ptr<Widgt>( new Widgt), priority() )// C++ 不能保证上述参数核算的顺序,比如有如下两种:// 1, 调用 priority() // priority 异常不会导致资源泄露// new Widgt() // 构造 shared_ptr// 2, new Widgt()// 调用 priority() // proority 异常会导致资源泄露// 构造 shared_ptrshared_ptr<Widgt> pw( new Widgt); processWidgt( pw, priority );
作者:呆呆的张先生
链接:https://www.jianshu.com/p/0e6db11a7fc4