使用C++11智能指针时要避开的10大错误

发表于:2016-8-24 11:25

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:欣仔    来源:51Testing软件测试网采编

  错误#6:删掉被shared_ptr使用的裸指针!
  你可以使用shared_ptr.get()这个api从一个shared_ptr获得一个裸指针的句柄。然而,这是非常冒险的,应该尽量避免这种情况。看看下面这段代码:
  void StartJob()
  {
  shared_ptr<aircraft> pAircraft(new Aircraft("F-16"));
  Aircraft* myAircraft = pAircraft.get(); // returns the raw pointer
  delete myAircraft; // myAircraft is gone
  }
  </aircraft>
  一旦我们从这个共享指针中获取到对应的裸指针(myAircraft),我们可能会删掉它。然而,当这个函数结束后,共享指针pAircraft就会因为超出作用域而去试图删除myAircraft这个已经被删除过的对象,而这样做的结果就是我们非常熟悉的ACCESS VIOLATION(非法访问)!
  建议 – 在你从共享指针中获取对应的裸指针之前请仔细考虑清楚。你永远不知道别人什么时候会调用delete来删除这个裸指针,到那个时候你的共享指针(shared_ptr)就会出现Access Violate(非法访问)的错误。
  错误#7:当使用一个shared_ptr指向指针数组时没有使用自定义的删除方法!
  看看下面这段代码:
  void StartJob()
  {
  shared_ptr<aircraft> ppAircraft(new Aircraft[3]);
  }
  </aircraft>
  这个共享指针将仅仅指向Aircraft[0] —— Aircraft[1]和Aircraft[2]将会在智能指针超出作用域时未被删除而造成内存泄露。如果你在使用Visual Studio 2015,就会出现堆损坏(heap corruption)的错误。
  建议 – 保证在使用shared_ptr管理一组对象时总是传递给它一个自定义的删除方法。下面这段代码就修复了这个问题:
  void StartJob()
  {
  shared_ptr<aircraft> ppAircraft(new Aircraft[3], [](Aircraft* p) {delete[] p; });
  }
  </aircraft>
  错误#8:在使用共享指针时使用循环引用!
  在很多情况下,当一个类包含了shared_ptr引用时,就有可能陷入循环引用。试想以下场景:我们想要创建两个Aircraft对象,一个由Maverick驾驶而另一个是由Iceman驾驶的(我忍不住要引用一下《壮志凌云》(TopGun)!!!)。Maverick和Iceman的僚机驾驶员(Wingman)互相指向对方。
  所以我们最初的设计会在Aircraft类中引入一个指向自己的shared_ptr。
  class Aircraft
  {
  private:
  string m_model;
  public:
  int m_flyCount;
  shared_ptr<Aircraft> myWingMan;
  ….
  然后在main()函数中,创建Aircraft型对象Maverick和Goose,然后给每个对象指定他们的wingman:
  int main()
  {
  shared_ptr<aircraft> pMaverick = make_shared<aircraft>("Maverick: F-14");
  shared_ptr<aircraft> pIceman = make_shared<aircraft>("Iceman: F-14");
  pMaverick->myWingMan = pIceman; // So far so good - no cycles yet
  pIceman->myWingMan = pMaverick; // now we got a cycle - neither maverick nor goose will ever be destroyed
  return 0;
  }
  </aircraft>
  当main()函数返回时,我们希望的是这两个共享指针都被销毁——但事实是它们两个都不会被删除,因为它们之间造成了循环引用。即使这两个智能指针本身被从栈上销毁,但由于它们指向的对象的引用计数都不为0而使得那两个对象永远不会被销毁。
  下面是这段程序运行的输出结果:
  Aircraft type Maverick: F-14 is created
  Aircraft type Iceman: F-14 is created
  所以应该怎么修复这个Bug呢?我们应该替换Aircraft类中的shared_ptr为weak_ptr!下面是修改后的main()程序再次运行的输出结果:
  Aircraft type Maverick: F-14 is created
  Aircraft type Iceman: F-14 is created
  Aircraft type Iceman: F-14 is destroyed
  Aircraft type Maverick: F-14 is destroyed
  注意到如何销毁两个Aircraft对象了吗。
  建议 – 在设计类的时候,当不需要资源的所有权,而且你不想指定这个对象的生命周期时,可以考虑使用weak_ptr代替shared_ptr。
  错误#9:没有删除通过unique_ptr.release()返回的裸指针!
  Release()方法不会销毁unique_ptr指向的对象,但是调用Release后unique_ptr则从销毁对象的责任中解脱出来。其他人(你!)必须手动删除这个对象。
  下面这段代码会出现内存泄露,因为Aircraft对象会一直存活,即使main()已经退出。
  int main()
  {
  unique_ptr<aircraft> myAircraft = make_unique<aircraft>("F-22");
  Aircraft* rawPtr = myAircraft.release();
  return 0;
  }
  </aircraft>
  建议 – 无论何时,在对unique_ptr使用Release()方法后,记得一定要删除对应的裸指针。如果你是想要删掉unique_ptr指向的对象,可以使用unique_ptr.reset()方法。
  错误#10:在调用weak_ptr.lock()的时候没检查它的有效性!
  在使用weak_ptr之前,你需要调用lock()方法来获取这个weak_ptr。lock()方法的本质是把这个weak_ptr升级为一个shared_ptr,这样你就可以像使用shared_ptr一样使用它了。然而,当weak_ptr指向的这个shared_ptr对象不再有效的时候,这个weak_ptr就为空了。使用一个失效的weak_ptr进行任何调用都会造成ACESS VIOLATION(非法访问)。
  举个例子,在下面这段代码中,名为“myWingMan”的weak_ptr指向的这个shared_ptr,在调用pIceman.reset()时已经被销毁。如果此时调用这个weak_ptr执行任何操作,都会造成非法访问。
int main()
{
shared_ptr<aircraft> pMaverick = make_shared<aircraft>("F-22");
shared_ptr<aircraft> pIceman = make_shared<aircraft>("F-14");
pMaverick->myWingMan = pIceman;
pIceman->m_flyCount = 17;
pIceman.reset(); // destroy the object managed by pIceman
cout << pMaverick->myWingMan.lock()->m_flyCount << endl; // <span style="color: #ff0000;">ACCESS VIOLATION</span>
return 0;
}
</aircraft>
  这个问题的修复方法很简单,在使用myWingMan这个weak_ptr之前进行一下有效性检查就可以了。
  if (!pMaverick->myWingMan.expired())
  {
  cout << pMaverick->myWingMan.lock()->m_flyCount << endl;
  }
  校正:我的很多读者指出,上面这段代码不能在多线程的环境下使用 – 如今99%的软件都使用了多线程。weak_ptr可能会在被检查有效性之后、获取lock返回值之前失效。非常感谢我的读者们指出这个问题!我将采用Manuel Freiholz给出的解决方案:在使用shared_ptr之前,调用lock()函数之后再检查一下shared_ptr是否为空。
  shared_ptr<aircraft> wingMan = pMaverick->myWingMan.lock();
  if (wingMan)
  {
  cout << wingMan->m_flyCount << endl;
  }
  建议 – 一定要检查weak_ptr是否有效 — 其实就是在使用共享指针之前,检查lock()函数的返回值是否为空。
  所以,接下来是什么呢?
  如果你想学习更多关于C++11智能指针的细节或者C++11的更多知识,我向你推荐下面这些书。
  1. C++ Primer (5th Edition) by Stanley Lippman (译者注:C++ Primer(第五版),作者:Stanley Lippman)
  2. Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14 by Scott Meyers (译者注:C++模板进阶指南:42个改善C++11和C++14用法的细节,作者:Scott Meyers)
  希望你在探索C++11特性的旅途中一切顺利。如果你喜欢这篇文章请分享给你的朋友们!
22/2<12
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号