C++11智能指针的常见误区 Top10

我很喜欢C++11全新的智能指针,对于那些苦恼于自己管理内存的开发者来说,这是天大的好事。但是,在广泛使用C++11智能指针的两年里,因为使用方法不当而导致程序的效率下降甚至直接crash掉的事情发生过不知道多少此。为了给大家参考,举出下面的一些例子。

误区1:在使用unique_ptr就足够的情况下使用shared_ptr

我最近交接的一些业务中,甚至出现全部对象的生成都是使用shared_ptr,但是分析代码后发现,有大约9成shared_ptr包裹的资源都不需要也没有被共用。
这样做,至少有以下两个问题:

  1. 程序的某一部分通过另一个共用指针改变额资源,但是当前指针并没有发现资源已经变更,将会导致不可预料的错误。即使另一个共用指针保持指向的资源不变,他可能超乎预料的长时间持有该资源的引用,即使当前指针离开作用域被销毁,该无用资源一直没能被释放;
  2. shared_ptrunique_ptr的生成时间更长,占用资源更多,运行效率更低。因为在shared_ptr的内部多出了引用计数和控制域,而且它们必须是线程安全的。

误区2:以为通过shared_ptr共用的资源/对象是线程安全的

虽然shared_ptr内部的引用计数和控制域都是线程安全的,但对于被shared_ptr管理的多线程共用资源依然需要基本的同步处理才能保证线程安全。

误区3:使用auto_ptr

auto_ptr的使用是危险的,当今已经不推荐使用了。使用该指针进行按值传参时会自动调用拷贝构造函数,这会导致整个资源的管理权的转移,这会导致原指针的地址失效,当对它进行取值操作时将直接crash
因为unique_ptrauto_ptr有相同的作用,而且前者在编写代码时就能自动检测出错误,所以建议将所有使用auto_ptr的地方都替换成unique_ptr

int main()  
{
  auto_ptr<aircraft> myAutoPtr(new Aircraft("F-15"));
  SetFlightCountWithAutoPtr(myAutoPtr); // Invokes the copy constructor for the auto_ptr
  myAutoPtr->m_flyCount = 10; // CRASH !!!
}

误区4:shared_ptr初始化时并没有使用make_shared

shared_ptr<aircraft> pAircraft(new Aircraft("F-16")); // Two Dynamic Memory allocations - SLOW !!!

如果像上面那样初始化shared_ptr,会有两次动态的内存分配,一次是要作为被管理的新对象本身,另一次是shared_ptr构造函数生成的负责管理的指针对象。
但是C++编译器针对shared_ptr其实可以一次性生成一个较大的内存空间直接存放这两个对象,代码如下所示。

shared_ptr<aircraft> pAircraft = make_shared<aircraft>("F-16"); // Single allocation - FAST !

误区5:生成对象后没有马上将地址分配给shared_ptr

思考一下以下的例子。

int main()
{
  Aircraft* myAircraft = new Aircraft("F-16");
 
  shared_ptr<aircraft> pAircraft(myAircraft);
  cout << pAircraft.use_count() << endl; // ref-count is 1
 
  shared_ptr<aircraft> pAircraft2(myAircraft);
  cout << pAircraft2.use_count() << endl; // ref-count is 1
 
  return 0;
}

显然,当main函数运行结束时,这两个shared_ptr都会尝试释放同一个对象的内存(计数器变为0),这将会导致程序crash。因此就算你不使用make_shared初始化shared_ptr,也至少保证在生成对象后马上初始化shared_ptr,保证之后绝不用这个原始的指针生成另外的任何智能指针。

误区6:尝试获取shared_ptr所管理的原始指针

使用shared_ptr.get()可以获取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
}

crash原因跟误区5如出一辙

误区7:对于指针数组使用shared_ptr的时候不使用自定义的析构函数

思考一下下面的例子

void StartJob()
{
  shared_ptr<aircraft> ppAircraft(new Aircraft[3]);
}

在这里,shared_ptr只指向了Aircraft[0],而数组的其他两个元素将不会被释放内存,从而造成内存泄露。应该参照下面的写法。

void StartJob()
{
  shared_ptr<aircraft> ppAircraft(new Aircraft[3], [](Aircraft* p) {delete[] p; });
}

误区8:使用shared_ptr时要避免出现循环引用

思考下下面的例子

class Aircraft
{
private:
       string m_model;
public:
       int m_flyCount;
       shared_ptr<Aircraft> myWingMan;
…
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;
}

显然,当函数调用结束后,这两个新对象都不会被释放资源。因此当没有确实的必要获取对象的强引用(即保证对象一定要是存活的)的情况下,应该尽量使用weak_ptr,如下。

class Aircraft
{
private:
       string m_model;
public:
       int m_flyCount;
       weak_ptr<Aircraft> myWingMan;
…

误区9:没有释放unique_ptr.release()返回的原始指针

因为release()方法并没有把unique_ptr所管理的对象释放掉,只是将unique_ptr和它管理的资源对象解除联系而已,所以需要你手动释放才行。

int main()
{
  unique_ptr<aircraft> myAircraft = make_unique<aircraft>("F-22");
  Aircraft* rawPtr = myAircraft.release();
  return 0;
}

显然,myAircraft将一直存活下去。

误区10:调用weak_ptr.lock()时没有确认是否有效

在使用weak_ptr之前,调用它的lock()方法是有必要的,它实际上是获取并替换对应的对象的当前真实的值。但如果这时候这个对象已经被释放掉了,weak_ptr就会变成空指针,如果对它进行取值操作,结果可想而知。

class Aircraft
{
private:
       string m_model;
public:
       int m_flyCount;
       weak_ptr<Aircraft> myWingMan;
…
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; // ACCESS VIOLATION
 
  return 0;
}

但也不能保证两次lock()之间对象一定不会被释放(第一次取出用来判空,第二次才用来真正访问),正确做法应该是以下这样的。

shared_ptr<aircraft> wingMan = pMaverick->myWingMan.lock();
if (wingMan)
{
  cout << wingMan->m_flyCount << endl;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值