全面推行使用智能指针的权利

今天,我们讨论了使用的功能返回一个指向下一个潜在的错误。如果有一个函数返回一个指针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模板,它表示单个对象。
  • 避免循环引用
  • 在制备指针返回到功能时,它应该返回一个智能指针而不是原始指针,为了落实没事的结果。


posted @ 2015-12-09 13:00  blfshiye  阅读(154)  评论(0编辑  收藏  举报