使用C++11智能指针时要避开的10大错误
作者:网络转载 发布时间:[ 2016/8/24 11:38:14 ] 推荐标签:指针 C++ .NET
错误#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特性的旅途中一切顺利。如果你喜欢这篇文章请分享给你的朋友们!
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系SPASVO小编(021-61079698-8054),我们将立即处理,马上删除。
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11热门文章
常见的移动App Bug??崩溃的测试用例设计如何用Jmeter做压力测试QC使用说明APP压力测试入门教程移动app测试中的主要问题jenkins+testng+ant+webdriver持续集成测试使用JMeter进行HTTP负载测试Selenium 2.0 WebDriver 使用指南