现在我们讨论下使用返回指针的函数的潜在错误。假设有一个函数返回一个指向某个MyClass类型的对象的指针。
MyClass* MyFactoryClass::Create(const Inputs& inputs);
这个函数的一个非常显而易见的问题是,它的调用者是否负责删除这个对象?或者说这个指针所指向的MyClass类的实例是MyFactoryClass所拥有的实例?这个问题显然应该在声明这个函数的头文件中以注释的形式说明。但在软件的世界里,实际上很少能够做到这样。但是,即使函数的作者确实提供了一个注释,表示这个函数在堆上创建了一个新对象,并且由它的调用者负责删除这个对象,我们将会发现自己面临这样一种处境:每当我们接到一个由函数调用所返回的指向某个对象的指针时,我们需要记得检查注释(或者在没有注释时,检查代码本身)来推断是否由我们负责删除这个对象。正如前面所说的,我们应该更多地依赖编译器而不是程序员。因此,实行这个对象所有权的一种可靠方法是让函数返回一个智能指针。例如:
RefCountPtr<MyClass> MyFactoryClass::Create(const Inputs& inputs);
这种设计使函数所返回的对象的所有权毫无争议,不会留下内存泄露的机会。另一方面,如果觉得引用计数指针速度过慢不适合需要,也可以返回一个作用域指针。但这样就产生了一个问题:ScopedPtr<MyClass> 无法被复制,因此不能按照传统方法返回:
ScopedPtr<MyClass> MyFactoryClass::Create(const Inputs& inputs) { ScopedPtr<MyClass> result(new MyClass(inputs)); return result; //无法通过编译 }
因此,解决这个问题的方法如下:
ScopedPtr<MyClass> result;//创建一个空的作用域指针 //填充它 void MyFactoryClass::Create(const Inputs& inputs,ScopedPtr<MyClass>& result);
我们创建一个包含NULL值的作用域指针,并让MyFactoryClass::Create()方法填充他。这个方法也不会使这个函数所创建的对象的所有权出现错误。如果不确定应该返回哪种指针,可以选择下列方案之一:
- 如果需要,返回速度更快的ScopedPtr,并使用它的Release()方法转移所有权。
- 同时通过两种方法
还有一种相反的情况是,SomeClass::Find()方法返回一个指向一个对象的指针,但用户并不拥有这个对象的所有权:
//返回一个指向一个结果的指针,调用者“并不拥有这个结果的所有权” MyClass* SomeClass::Find(const Inputs& inputs);
这种情况下,这个函数所返回的指针,指向属于SomeClass内部的某个对象的一个对象。
对于上述第一个问题,SomeClass类认为自己将负责删除它刚返回的那个指针所指向的MyClass实例,因此会在未来的某个时刻将它删除。在这种情况下,如果这个函数的用户将删除他所接收到的指针,这个实例将被删除不止一次,这显然不是个好主意。其次,这个实例可能是一个vector模板中使用new[]操作符(带方括号)创建的一个MyClass对象数组的一部分,而现在我们将使用不带方括号的delete操作符从这个数组中删除一个对象。这同样不是个好的做法。最后,MyClass实例可能是在堆栈上创建的,根本不应该使用delete操作符进行删除。
在这种情况下,任何试图删除我们并不拥有的对象的行为(直接删除,或者把它赋值给一个将接收对象所有权的任何类型的智能指针)将会导致灾难。返回这种指针的一种合适的方法是返回一个”半智能“指针,它并不拥有它所指向的对象的所有权。
总结:为了避免内存泄露,建议遵从以下规则:
- 每次使用new操作符创建一个对象时,立即把结果赋值给一个智能指针(推荐使用引用计数指针或作用域指针)
- 使用new操作符时不要带方括号。如果创建一个数组,可以创建一个新的vector模板,它表示单个对象。
- 避免循环引用
- 在编写返回一个指针的函数时,应该返回一个智能指针而不是原始指针,以实行结果的所有权。
用智能指针实行所有权