原以为智能指针可以完全保证对堆对象的正确引用和释放,但智能指针不是万能的。以shared_ptr
为例,以下几种使用情况会使得代码重复释放堆对象。
1.滥用原始指针构造shared_ptr
#include <iostream>
#include <memory>
class A {};
using Ptr = std::shared_ptr<A>;
int main()
{
A *ps = new A();
Ptr p1(ps);
Ptr p2(p1);
Ptr p3 = p2;
Ptr p4;
p4 = p3;
Ptr p5(ps);
std::cout << p1.use_count() << std::endl;
std::cout << p2.use_count() << std::endl;
std::cout << p3.use_count() << std::endl;
std::cout << p4.use_count() << std::endl;
std::cout << p5.use_count() << std::endl;
return 0;
}
运行结果如下:
可以看到,在使用拷贝构造或赋值运算符=
的时候,shared_ptr
会共享并更新控制块。而当使用原始指针构造一个新的shared_ptr
时,会创建并初始化一个新的控制块。
因此在程序最后,当shared_ptr
要析构自己指向的堆对象的时候,会重复释放内存,从而引起运行时错误。
构造
shared_ptr
时,如果其指向的对象已被别的shared_ptr
所指,请使用拷贝构造或赋值运算初始化新指针。
2.类内包含指向自身的shared_ptr
#include <iostream>
#include <memory>
class A;
using SPtr = std::shared_ptr<A>;
using WPtr = std::weak_ptr<A>;
class A {
public:
A() : sptr(this), wptr(sptr)
{
std::cout << "in construct :" << std::endl;
std::cout << sptr.use_count() << std::endl;
std::cout << this << " " << sptr << " " << wptr.lock() << std::endl;
}
private:
SPtr sptr;
WPtr wptr;
};
int main()
{
A *p = new A();
SPtr ptr(p);
std::cout << "in main :" << std::endl;
std::cout << ptr.use_count() << std::endl;
return 0;
}
运行结果如下:
原因和上面类似,一个堆对象被两个shared_ptr
控制块管理,最终导致重复释放内存。
如果main函数是这样的,也会运行出错。原因是当对象被delete
释放的时候,对象中的shared_ptr
也被释放,此时它所指向的对象(this
)又会被释放一次。
int main()
{
A *p = new A();
std::cout << p << std::endl;
delete p;
return 0;
}
请诸君别使用这种奇葩的用法。