动态内存
C++中程序用堆来存储动态分配(dynamically allocate)的对象——即那些在程序运行时分配的对象。
动态内存的生存期由程序控制,也就是当动态对象不再使用时,我们必须显示的销毁它们。
But众所周知(王小波句式),正确的管理动态内存是非常棘手的。如果忘了释放内存,就会导致内存泄漏;如果在还有指针引用内存时就去释放那块内存,那么那个指针就会变为一个引用非法内存的指针。thorny problem!
智能指针原理
So, 为了更容易更安全的使用动态内存,新的标准库提供了智能指针(smart pointer)类型来管理动态对象。
智能指针的行为类似常规指针,区别是它负责自动释放所指向的对象。
那么smart pointer是如何做到自动释放内存的呢?
其实,智能指针是一个类类型,假如给它起个类名叫SmartPtr吧。在这个SmartPtr类中,有一个private成员变量,这个变量是另外一种类类型的指针(就是说它是一个指针,指向一个类),然后通过这个指针来动态创建类的对象。
比如,这里创建了SmartPtr类和Animal类,SmartPtr中的成员变量ptr_为Animal类的一个指针。
代码如下:
#ifndef SMARTPTR #define SMARTPTR #include <iostream> //禁用赋值与复制 #define DISALLOW_COPY_AND_ASSIGN(TypeName) TypeName(const TypeName&); void operator=(const TypeName&) class Animal { public: Animal() { std::cout << "construct......." << std::endl; } ~Animal() { std::cout << "destruct ... " << std::endl; } void run() { std::cout << "running......." << std::endl; } }; class SmartPtr { public: //SmartPtr针对的是heap上的对象 SmartPtr(Animal *ptr); ~SmartPtr(); Animal &operator*(); const Animal &operator*() const; Animal *operator->(); //重载SmartPtr这个类的-> const Animal *operator->() const; private: DISALLOW_COPY_AND_ASSIGN(SmartPtr); Animal *ptr_; }; #endif /*SMARTPTR*/ SmartPtr::SmartPtr(Animal *ptr) :ptr_(ptr) { } SmartPtr::~SmartPtr() { delete ptr_; //SmartPtr析构时释放指向Animal类的指针,从而释放内存 } Animal &SmartPtr::operator*() { return *ptr_; } //const 版本 const Animal &SmartPtr::operator*() const { return *ptr_; } Animal *SmartPtr::operator->() { return ptr_; } //const版本 const Animal *SmartPtr::operator->() const { return ptr_; }
然后我们编写一个异常处理函数来测试一下:
#include <stdexcept> #include "SmartPtr.hpp" using namespace std; int main(int argc, const char *argv[]) { try{ //这里的ptr离开try时一定会被销毁 //从而导致Animal对象一定会被析构 SmartPtr ptr(new Animal); throw runtime_error("error"); }catch(runtime_error &e) { cout << e.what() << endl; /* construct... destruct... error */ } return 0; }
这里补充一点:在异常处理函数中,如果在栈展开过程中退出了某个块(执行到throw语句),编译器将会负责确保在这个块中的对象能被正确的销毁。如果某个局部对象的类型是类类型,则该对象的析构函数将会被自动调用。
所以,上面程序中当执行到throw语句时,直接跳到catch中继续运行。那么原来try中的东东会确保被销毁,也就是编译器自动会去调用ptr这个对象的析构函数,从而执行了delete ptr_, 所以存储Animal对象的内存就被自动释放了。
一句话,智能指针利用了栈对象的生存期,将资源的获取放在构造函数里面,资源的释放放在析构函数里面,从而保证了资源一定会被正确释放。
最后,这也就是C++中的RAII(Resource Acquirement Is Initialization)
RAII要求,资源的有效期与持有资源的对象的生命期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄露问题。