Hazard
如图,这是 shared_ptr
的结构:
template <typename T>
class SharedPtr
{
public:
SharedPtr() : t_(nullptr), count_(nullptr) {}
explicit SharedPtr(T *t) : t_(t), count_(nullptr)
{
if (t)
{
count_ = new int(1);
}
}
explicit SharedPtr(const SharedPtr &rhs)
{
t_ = rhs.t_;
count_ = rhs.count_;
if (count_)
{
(*count_)++;
}
}
~SharedPtr()
{
reset();
}
SharedPtr &operator=(const SharedPtr &rhs)
{
if (this == &rhs)
{
return *this;
}
reset();
t_ = rhs.t_;
count_ = rhs.count_;
if (count_)
{
(*count_)++;
}
return *this;
}
SharedPtr &operator*()
{
return *this;
}
T *operator->()
{
return t_;
}
T *get() const
{
return t_;
}
int use_count() const
{
return count_ ? *count_ : 0;
}
void reset()
{
if (count_)
{
(*count_)--;
cout << typeid(T).name() << ' ' << (*count_) << '\n';
if (*count_ == 0)
{
delete t_;
delete count_;
}
}
}
private:
T *t_;
int *count_;
};
假如说我们使用循环引用,i.e. 两结构体中的指针分别指向对方,就会造成双方(i.e. father 和 son)都以为自己还有其它指针还指向自己,从而无法实现析构,造成内存泄露:
class Son;
class Father {
public:
SharedPtr<Son> son_;
father(){
cout << __FUNCTION__ << endl;
}
~Father(){
cout << __FUNCTION__ << endl;
}
}
class Son{
public:
SharedPtr<Father> father_;
Son(){
cout << __FUNCTION__ << endl;
}
~Son(){
cout << __FUNCTION__ << endl;
}
}
int main() {
auto son = SharedPtr<Son>(new Son());
auto father = SharedPtr<father>(new Father());
son->father_ = father;
father->son_ = son;
cout << "son: " << son.use_count() << endl;
cout << "father: " << father.use_count() << endl;
return 0
}
输出:
也就是说,析构的时候:
- father 先析构,使得 father 的 count 降到 1
- son 再析构,从而 son 的 count 降到 1
但是,两者的 count 都没有降到 0,因此没有析构,造成了内存泄漏。
我们需要使用 weak_ptr
来解决问题。
Solution: weak_ptr
weak_ptr
可以认为就是依附于某个 shard_ptr
的指针。当某个 weak_ptr
引用了 shared_ptr
时,此 shared_ptr
不会自增
最重要的成员函数:
use_count
: 返回所依附的shared_ptr
的count
expired
: 检查是否所依附的shared_ptr
已经无效lock
: 通过所依附的shared_ptr
新建一个shared_ptr
,并返回新建的shared_ptr
的指针
weak_ptr
的本质
weak_ptr
其实就是裸指针的简单封装。
由于使用(指向一个智能指针的)裸指针的时候,容易不小心使用悬垂指针(i.e. 智能指针已经失效了,然而你仍然试图解引用这个智能指针),因此我们特地将指向一个智能指针的裸指针封装成弱指针。
由于 weak_ptr
不会导致 shared_ptr
自增,自然没有循环引用的问题。当然,最重要的前提还是:程序员一定要尽量避免循环引用(不论用不用 weak_ptr
),假如必须有循环引用,那么就用 weak_ptr
。