有以下构造函数和析构函数:
BookEntry::BookEntry() : pImage(nullptr) , pClip(nullptr) { pImage = new Image(); pClip = new Clip(); } ~BookEntry() { delete pImage; delete pClip; }
构造函数在初始化列表将指针pImage
、pClip
置空,然后在函数内为指针初始化对象。在析构函数释放指针的资源,避免内存泄漏。
在正常情况下运行良好,但当pClip = new Clip();
这一步发生错误时,异常将会被BookEntry构造函数
的调用者。错误的原因可能是operator new
不能分配足够的内存,或是Clip
的构造函数发生异常。
__发生异常的BookEntry
对象并没有完成构造。C++仅能删除被完全构造的对象,即构造函数完全运行完毕,因此~BookEntry()
永久不会被调用,导致了内存泄漏。__如果尝试主动捕获异常并调用析构函数:
try { BookEntry * obj = new BookEntry(); } catch(...) { delete obj; throw; }
__new
首先通过operator new
分配堆内存,然后调用相应的构造函数,最后将内存首地址返回到指针。当BookEntry
的构造函数发生异常时,obj
仍然是空指针,delete obj
并不能释放掉已分配的堆内存。__因此对象必须在构造函数发生异常时,在构造函数内执行必要的清除工作。
BookEntry::BookEntry() : pImage(nullptr) , pClip(nullptr) { try { pImage = new Image(); pClip = new Clip(); } catch(...) { delete pImage;//删除空指针是安全的 delete pClip; throw; } }
如果是Clip
的构造函数发生异常,构造函数能够将已分配内存的pImage
资源释放,但对于Clip
对象中可能泄漏的资源,它的构造函数需要用同样的方法进行清除:构造函数在异常传递之前完成必要的清除工作。
当pImage
和pClip
是常量指针,必须要成员初始化列表完成构造,上述的方法不再适用。同时,初始化列表只允许表达式,try
和catch
必须转移到别的地方。
BookEntry::BookEntry() : pImage(initImage())//通过私有构造函数初始化对象 , pClip(initClip()) {} Image * BookEntry::initImage() { return new Image(); } Clip * BookEntry::initClip() { try { return new Clip(); } catch(...) {//因为`Clip`在`pImage`之后构造,其初始化函数负责释放`pImage`资源。 delete pImage; } }
class BookEntry { public: BookEntry(); ~BookEntry() = default;//资源自动释放 private: const auto_ptr<Image> pImage; const auto_ptr<Clip> pClip; } BookEntry::BookEntry() : pImage(new Image) , pClip(new Clip) {}
通过智能指针简化构造函数中的异常处理:当new Clip
发生异常时,智能指针pImage
是已经完全构造的局部对象,在离开生命周期后会自动释放掉,因此不需要再手动捕获异常来进行清除。同时由局部对象管理动态资源,省略了析构函数中的清除操作。