前言本文内容将专门针对内存管理,培养借还的好习惯,杜绝资源管理问题。文中所谓的资源,就是一旦使用,以后就必须归还给系统。如果不是这样,就会发生不好的事情。C++程序中常见的资源:内存的动态分配、文件描述符、互斥锁、图形页面中的字体和画笔、数据库连接、网络套接字,无论是哪种资源,重要的是当你不再使用它时,你必须把它归入系统是一个好习惯。细节01:用对象管理资源将资源放在析构函数中,交给析构函数释放资源假设一个类包含一个工厂函数,它获取对象的指针:A*createA();//返回指针,指向一个动态分配的对象。//删除它是调用者的责任。上面注释中提到,createA的调用者使用函数返回的对象后,负责删除它。现在考虑一个f函数来完成这个职责:voidf(){A*pa=createA();//调用工厂函数...//其他代码deletepa;//释放资源}这看起来很安全,但是在某些情况下,f函数可能无法执行deletepa语句,这将导致资源泄漏,例如以下情况:可能是因为“...“区域;也许是因为“...”区的循环语句过早地退出了continue或goto语句;可能因为“...”区的语句抛出异常,无法执行删除。当然,这种错误是可以通过仔细编写程序来避免的,但是你要想到时间久了代码可能会被修改。如果新手不注意这类情况,难免会再次发生内存泄漏。可能性。为确保A返回的所有资源都被回收,我们需要将资源放入对象中。当对象离开作用域时,对象的析构函数会自动释放资源。“智能指针”是个好帮手,让它来管理指针对象。对于在堆内存中动态分配(new)的对象,指针对象在离开作用域时不会自动调用析构函数(需要手动delete)。构造函数回收资源,我们需要使用“智能指针”特性。常用的“智能指针”有如下三种:std::auto_ptr(C++98提供,C++11建议丢弃)std::unique_ptr(C++11提供)std::shared_ptr(由C++11提供)std::auto_ptr演示如下Howtousestd::auto_ptrtoavoidpotentialresourceleaksintheffunction:voidf(){std::auto_ptrpa(createA());//调用工厂函数...//一如既往地使用pa}//离开作用域后,通过auto_ptr的析构函数自动删除pa;这个简单的例子演示了“用对象管理资源”的两个关键思想:获取资源后立即放入管理对象中。上面代码中createA返回的resource被当做它的managerauto_ptr的初始值,立即放入management对象中。托管对象使用析构函数来确保资源释放。不管控制流如何离开块,一旦对象被销毁(例如,当对象超出作用域时),它的析构函数自然会被自动调用,资源就会被释放。为什么在C++11中不推荐使用auto_ptr?当然auto_ptr是有缺陷的,以后不建议再用了。auto_ptr有一个不寻常的属性:如果它们通过“复制构造函数或赋值运算符函数”复制,它们将变为null,并且复制的指针获得资源的唯一所有权!请参见以下示例:std::auto_ptrpa1(createA());//pa1指向createA返回对象std::auto_ptrpa2(pa1);//现在pa2指向对象,pa1将被设置为nullpa1=pa2;//现在pa1指向一个对象,pa2将被设置为null,这是一种奇怪的复制行为。如果再次使用指向null的指针,势必会导致程序崩溃。意味着auto_ptr不是管理动态分配资源的法宝。std::unique_ptrunique_ptr也采用所有权模型,但在使用时,直接禁止通过拷贝构造函数或赋值运算符函数拷贝指针对象。下面的例子编译时会报错:std::unique_ptrpa1(createA());//pa1指向createA返回的对象std::unique_ptrpa2(pa1);//编译错误!pa1=pa2;//编译错误!std::shared_ptrshared_ptr使用拷贝构造函数或赋值运算符函数后,引用计数会累加,两个指针对象指向同一块内存,这一点不同于unique_ptr和auto_ptr。voidf(){std::shared_ptrpa1(createA());//pa1指向createA的返回对象std::shared_ptrpa2(pa1);//引用计数+1,pa2和pa1指向同一个A内存pa1=pa2;//引用计数+1,pa2和pa1指向同一块内存}当对象离开作用域时,shared_ptr会将引用计数值设置为-1,直到引用计数值为0时才会删除该对象。由于shared_ptr在释放空间的时候会提前判断引用计数值的大小,所以不会出现多次删除一个对象的错误。总结-请记住防止资源泄漏,请使用RAII(ResourceAcquisitionIsInitaliaztion-资源获取的时机是初始化的时机)对象,它们在构造函数中获取资源,并在析构函数中释放资源。两个推荐使用的RAII类分别是std::unique_ptr和std::shared_ptr。前者不允许复制动作,后者允许复制动作。但是不推荐使用std::auto_ptr。如果选择auto_ptr,复制操作将使其(复制的对象)指向null。细节02:注意资源管理类中的复制行为假设。我们使用C语音的API函数来处理Mutex类型的互斥对象。有两个可用的锁定和解锁功能:voidlocak(Mutex*pm);//锁定pm指的是互斥体voidunlock(Mutex*pm);//解锁互斥量为了确保一个锁定的互斥量永远不会被忘记解锁,我们不妨创建一个类来管理锁资源。这样的类必须遵守RAII代码,即“资源在构造时获取,在解构时释放”:classLock{public:explicitLock(Mutex*pm)//Constructor:pMutex(pm){lock(pMutex);}~Lock()//析构函数{unlock(pMutex);}私人:互斥*pMutex;};这样定义的Lock符合RAII方式:Mutexm;//定义你需要的互斥锁。..{//创建一个本地块作用域Lockm1(&m);//Lockthemutex...}//离开块作用域时自动解锁mutex这很好,但是如果Lock对象被复制了,会发生什么?锁定m1(&m);//锁定mLockm2(&m1);//将m1复制到m2,会发生什么?这就是我们需要思考和面对的:“当一个RAII对象被复制时会发生什么?”大多数时候你会选择以下两种可能:禁止复制。如果RAII不允许被拷贝,那么我们需要将类的拷贝构造函数和赋值运算符声明为private。使用引用计数。有时我们想持有资源直到它的最后一个对象被消耗掉。在这种情况下,复制RAII对象时,资源的“引用计数”应该增加。std::shared_ptr就是这样做的。如果前面提到的Lock打算使用引用计数,它可以使用std::shared_ptr来管理pMutex指针,不幸的是std::shared_ptr的默认行为是“当引用计数为0时删除它的引用”,那不是我们期望的行为,因为释放Mutex的操作是解锁而不是删除。幸运的是std::shared_ptr允许指定自定义删除方法,即函数或函数对象。如下:classLock{public:explicitLock(Mutex*pm):pMutex(pm,unlock)//用某个Mutex初始化shared_ptr,//并使用unlock函数作为deleter。{锁(pMutex.get());//get获取指针地址}private:std::shared_ptr
