逐步实现带简单的智能指针,理解CPP智能指针底层原理!


逐步实现带简单的智能指针,理解CPP智能指针底层原理!

不带引用计数的指针指针实现

不带引用计数的智能指针的实现方法比较容易想到,就是使用一个类来维护一个对象指针
并且这个指针能调用裸指针的方法,下面是一个简单的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
template<typename T>
class SmartPtr
{
public:
SmartPtr
SmartPtr(){ ptr_ = nullptr;}
explicit SmartPtr(T* pointer)
{
ptr_ = pointer;
}
SmartPtr(const SmartPtr<T>&) = delete;
SmartPtr(SmartPtr<T>&& t)
{
ptr_ = t.ptr_;
t.ptr_ = nullptr;
}
~SmartPtr(){ delete ptr_; }
SmartPtr<T>& operator=(const SmartPtr<T>&) = delete;
SmartPtr<T>& operator=(SmartPtr<T>&& t)
{
ptr_ = t.ptr_;
t.ptr_ = nullptr;
return *this;
}
T& operator*()
{
return *ptr_;
}
T* operator->()
{
return ptr_;
}
private:
T* ptr_;
};

对这个实现有几个要注意的地方

  1. 构造函数带有explicit关键字,根据C++标准我们禁止裸指针到智能指针的隐式转换
  2. ->运算符的重载,编译器做了特殊的处理,我们只需要返回对应的指针即可,编译器将自动解释为用这个指针再去调用对应的函数
  3. 只提供右值的复制构造函数,它直接接管右值管理的对象并清除右值的所有权
  4. 删除赋值函数,提供带右值引用参数的赋值函数

下面是测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
class A
{
public:
A()
{
cout << "构造了A" << endl;
}
~ A() {
cout << "析构了A" << endl;
}
void show()
{
cout << "我是A" << endl;
}

};
int main()
{
SmartPtr<A> p1(new A);
p1->show();
}

我们可以看到代码运行的非常好

带引用计数的指针指针实现

我们需要一个引用计数器将一个整数和对象指针绑定起来
故设计一个引用计数器类

1
2
3
4
5
6
7
8
9
10
11
12
13
template<typename T>
class RefCnt
{
public:
RefCnt(T* ptr):ptr_(ptr),count(1)
{
}
void addRef(){ ++count;}
int delRef() { --count; return count; }
private:
int count;
T* ptr_;
};

接下来每个智能指针对象出来维护一个裸指针还要维护它对应的引用计数器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
    template<typename T>
class Sharedptr
{
public:
explicit Sharedptr(T* ptr)
{
ref = new RefCnt<T>(ptr);
ptr_ = ptr;
}
Sharedptr(const Sharedptr& sptr)
{
sptr.ref->addRef();
ptr_ = sptr.ptr_;
ref = sptr.ref;
}
Sharedptr<T>& operator=(const Sharedptr<T>& sptr)
{
if (this->ptr_ == sptr.ptr_)//如果是同一块资源,不需要任何操作
{
return *this;
}
if (ref->delRef() == 0)//释放当前管理的对象那个
{
delete ptr_;
delete ref;
}
//接管新的对象
sptr.ref->addRef();
ptr_ = sptr.ptr_;
ref = sptr.ref;
}
~Sharedptr()
{
//这里我们可以断言,执行ref->delRef()的时候ref一定是还未被释放的
//因为赋值操作并不会导致delRef为空,赋值操作保证智能指针得到一个有意义的Ref
if (ref->delRef() == 0)
{
delete ptr_;
delete ref;
}
}

T& operator*()
{
return *ptr_;
}
T* operator->()
{
return ptr_;
}
private:
RefCnt<T>* ref;
T* ptr_;
};

注意我们并没有使用任何同步机制,因此我们这个智能指针不是线程安全的,而std::shared_ptr和std::weak_ptr的计数是线程安全的

不难发现如果将对象交给智能指针后,又获得这个对象的裸指针创建新的智能指针对象管理,那么这个指针的引用计数器将是不正确的
最容易犯这种错误的场景就是this指针,多线程环境种我们在类的成员函数中直接使用this指针是不安全的,因为对象可能已经被析构了,我们需要使用智能指针管理对象,
然而
shared_ptr<classname> p(this)
并不会生效,因为这样使用计数值为1,这并不正确
正确返回this的shared_ptr的做法是:让目标类通过std::enable_shared_from_this类,然后使用基类的 成员函数shared_from_this()来返回this的shared_ptr
如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <memory>

using namespace std;

class A: public std::enable_shared_from_this<A>
{
public:
shared_ptr<A>GetSelf()
{
return shared_from_this();
}
~A()
{
cout << "Deconstruction A" << endl;
}
};

那么std::enable_shared_from_this的实现原理是什么呢

  • 这个类模板中包含了一个 weak_ptr<T>在创建这个类的子类的时候,weak_ptr<T>就会观察会指向对象及其计数器,在需要使用时调用shared_from_this()方法将weak_ptr提升为shared_ptr即可,由于是提升来的,不会有计数错误的问题

一旦使用std::enable_shared_from_this这个类的对象必须交给智能指针管理,否则调用shared_from_this()会因为找不到ref而崩溃

此文章仅供参考


Author: 5helter
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source 5helter !
  TOC