我很喜欢C++11
全新的智能指针,对于那些苦恼于自己管理内存的开发者来说,这是天大的好事。但是,在广泛使用C++11
智能指针的两年里,因为使用方法不当而导致程序的效率下降甚至直接crash
掉的事情发生过不知道多少此。为了给大家参考,举出下面的一些例子。
误区1:在使用unique_ptr就足够的情况下使用shared_ptr
我最近交接的一些业务中,甚至出现全部对象的生成都是使用shared_ptr
,但是分析代码后发现,有大约9成
被shared_ptr
包裹的资源都不需要也没有被共用。
这样做,至少有以下两个问题:
- 程序的某一部分通过另一个共用指针改变额资源,但是当前指针并没有发现资源已经变更,将会导致不可预料的错误。即使另一个共用指针保持指向的资源不变,他可能超乎预料的长时间持有该资源的引用,即使当前指针离开作用域被销毁,该无用资源一直没能被释放;
shared_ptr
比unique_ptr
的生成时间更长,占用资源更多,运行效率更低。因为在shared_ptr
的内部多出了引用计数和控制域,而且它们必须是线程安全的。
误区2:以为通过shared_ptr共用的资源/对象是线程安全的
虽然shared_ptr
内部的引用计数和控制域都是线程安全的,但对于被shared_ptr
管理的多线程共用资源依然需要基本的同步处理才能保证线程安全。
误区3:使用auto_ptr
auto_ptr
的使用是危险的,当今已经不推荐使用了。使用该指针进行按值传参时会自动调用拷贝构造函数,这会导致整个资源的管理权的转移,这会导致原指针的地址失效,当对它进行取值操作时将直接crash
。
因为unique_ptr
和auto_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;
}