假设类Investment是各种关于投资的基类,股票类Stock继承自Investment。有一个工厂函数可以返回一个具体的Investment对象:
1 #include<iostream> 2 3 using namespace std; 4 5 class Investment // 关于投资的基类 6 { 7 public: 8 virtual ~Investment(); 9 }; 10 Investment::~Investment(){ } 11 12 class Stock : public Investment // 股票类,继承自Investment 13 { 14 15 }; 16 17 Investment* createInvestment() // 工厂函数,返回一个派生类对象(此处为了简化,其实最好定义一个工厂类做这个工作) 18 { 19 return new Stock; 20 } 21 22 void use() // 使用工厂函数返回的对象,完成工作后删除这个对象 23 { 24 Investment* pInv = createInvestment(); 25 // ... 26 delete pInv; 27 } 28 29 int main() 30 { 31 use(); // 正常运行 32 33 return 0; 34 }
正常情况下,代码正常运行。现在考虑这样一个事实:如果在// ...处出现了return语句,那么是不是delete就无法执行了,如此就造成了内存泄漏。很多情况下,即使函数设计非常严密,当前不会不出现这种类似情况,但是如果后期维护人员为了调试出现的问题,自行加入了导致delete不能运行的语句块呢?也就是说delete不能执行的情况总会发生,幸好我们还有如下方法可以避免这个问题:
把需要被释放的资源放入管理对象中,这样便可以依靠C++的“析构函数自动调用机制”确保资源被释放。
注意:
此方法适合管理动态分配的heap对象。
auto_ptr是个pointer-like对象,就是所谓的智能指针,会在一个模块结束后自动调用所指对象的delete,示例如下:
1 void use() // 使用工厂函数返回的对象,完成工作后删除这个对象 2 { 3 auto_ptr<Investment> pInv(createInvestment()); // 使用auto_ptr后不用显式调用delete了 4 // ... 5 }
上述代码体现了两个关键想法:
1 在获得资源后立刻放进管理对象中:即用指向具体对象的指针来初始化auto_ptr。其思想也被称为“资源获得时机便是初始化时机: Resource Acquisition Is Initialization: RAII”。
2 管理对象运行析构函数确保资源的到释放:一旦当对象离开其作用域时,对象的析构函数会被自动调用,再也不用担心内存泄漏了?当然也不是,如果析构函数出现问题呢,这就是另一个话题了。
我们总是在不经意间将同一对象delete不止一次,auto_ptr也考虑到了这个问题,其规定:如通过copy构造函数或者copy assignment操作符函数copy它们,那么被copy的那个指针就会变为null,就是说确保永远只有一份auto_ptr,就像如下:
1 void use() // 使用工厂函数返回的对象,完成工作后删除这个对象 2 { 3 auto_ptr<Investment> pInv(createInvestment()); // 使用auto_ptr后不用显式调用delete了 4 // ... 5 auto_ptr<Investment> pInv2(pInv); // 现在pInv2指向Stock对象,pInv为null 6 pInv = pInv2; // 现在pInv指向Stock对象,pInv2为null 7 }
但是对于某些类,比如STL容器,其要求可以同时有多个指针指向同一对象,此时,auto_ptr显得有些力不从心了,但是我们又有一个“带有计数功能的智能指针:reference-counting smart pointer: RCSP”,其可以计数共有多少指针指向同一对象,只有在计数为0时才自动释放该对象,那么use()就可以如下设计:
1 void use() // 使用工厂函数返回的对象,完成工作后删除这个对象 2 { 3 tr1::shared_ptr<Investment> pInv(createInvestment()); 4 tr1::shared_ptr<Investment> pInv2(pInv); // pInv和pInv2同时指向Stock对象 5 pInv = pInv2; // 同上,没有任何改变 6 }
注意:
auto_ptr和tr1::shared_ptr二者的析构动作都是执行delete而不是delete[],就是说如果动态分配的是数组,那么使用智能指针不会释放全部资源,C++并没有针对动态分配的数组做智能化释放处理,那是因为vector和string可以完全取代数组的工作。
最后有一个小点,如果我们想要使用智能指针所指向的原始指针,如何做呢?
有两种方式:
1 使用显式转换:get()
Investment* pInv2 = pInv.get();
2 自己设计隐式转换函数
一般而言,显式转换虽然麻烦,但是比较安全;隐式转换虽然比较方便,但是有时会造成难以发现的错误。