智能指针模拟实现

RAII:利用对象生命周期来控制程序资源,在构造时获取资源,析构的时释放资源

auot_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
template<class T>
class AutoPtr {
public:
AutoPtr(T* ptr = nullptr)
: _ptr(ptr) {}

~AutoPtr() {
if (_ptr)
delete _ptr;
}

AutoPtr(AutoPtr<T>& ap)
: _ptr(ap._ptr) {
// 转移资源管理权
ap._ptr = NULL;
}

AutoPtr<T>& operator=(AutoPtr<T>& ap) {
if (this != &ap) {
if (_ptr)
delete _ptr;
// 转移资源管理权
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}

// 像指针一样的操作
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
private:
T* _ptr;
};

Bug:

1
2
AutoPtr<T> ap(new T);
AutoPtr<T> copy(ap);// 拷贝后把ap对象的指针赋空了,通过ap对象访问资源时就会出bug(除非每次判断ap._ptr是否不为空)

所以auto_ptr根本用不了

C++11 unique_ptr

也叫scoped_ptr(boost库)

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
template<class T>
class UniquePtr {
public:
UniquePtr(T* ptr = nullptr)
: _ptr(ptr) {}

~UniquePtr() {
if (_ptr)
delete _ptr;
}

// unique_ptr并没有去实现类似scoped_array的unique_array,因为标准库中有vector
// T& operator[](size_t index) {
// return _ptr[index];
// }
// const T& operator[](size_t index) const {
// return _ptr[index];
// }

T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
private:
// C++11
UniquePtr(UniquePtr<T> const &) = delete;
UniquePtr & operator=(UniquePtr<T> const &) = delete;
private:
T * _ptr;
}

相比auto_ptr,简单粗暴的防拷贝,勉强可以使用,但没有从根本解决问题

C++11 shared_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
55
56
57
58
59
template <class T>
class SharedPtr {
public:
SharedPtr(T* ptr = nullptr)
: _ptr(ptr)
, _pRefCount(new int(1))
, _pMutex(new mutex) {}

~SharedPtr() { Release(); }

SharedPtr(const SharedPtr<T>& sp)
: _ptr(sp._ptr)
, _pRefCount(sp._pRefCount)
, _pMutex(sp._pMutex) {
AddRefCount();
}

SharedPtr<T>& operator=(const SharedPtr<T>& sp) {
if (_ptr != sp._ptr) {
Release();
// 共享管理新对象的资源,并增加引用计数
_ptr = sp._ptr;
_pRefCount = sp._pRefCount;
_pMutex = sp._pMutex;
AddRefCount();
}
return *this;
}

T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
int UseCount() { return *_pRefCount; }
T* Get() { return _ptr; }
private:
void AddRefCount() {
// 加锁或者使用加1的原子操作
_pMutex->lock();
++(*_pRefCount);
_pMutex->unlock();
}

void Release() {
bool deleteflag = false;
// 引用计数减1,如果减到0,则释放资源
_pMutex.lock();
if (--(*_pRefCount) == 0) {
delete _ptr;
delete _pRefCount;
deleteflag = true;
}
_pMutex.unlock();
if (deleteflag == true)
delete _pMutex;
}

int* _pRefCount; // 引用计数
T* _ptr; // 指向管理资源的指针
mutex* _pMutex; // 互斥锁
};

仿函数删除器

上面代码析构函数是用delete来释放资源的,但是我们可能用以下几种方式申请资源:
SharedPtr<int> p(new int);
SharedPtr<FILE> fp(fopen("./test","r"));
SharedPtr<int> mp = ((int*)malloc(sizeof(int)));
这时候使用delete来释放,可能会出错,这时候就需要自定义删除方式,我们只需对上面代码稍加修改即可:

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
// 定义我们自己的删除方式
template<class T>
class Delete {
public:
void operator()(T*& p) {
if (p != nullptr) {
delete p;
p = nullptr;
}
}
};

template<class T>
class Free {
public:
void operator()(T*& p) {
if (p != nullptr) {
free(p);
p == nullptr;
}
}
};

class Close {
public:
void operator()(FILE*& p) {
if (p != nullptr) {
fclose(p);
p = nullptr;
}
}
};

template <class T, class D = Delete<T>>
class SharedPtr {
public:
// 其他都不变,修改析构函数即可
void Release() {
bool deleteflag = false;
// 引用计数减1,如果减到0,则释放资源
_pMutex.lock();
if (--(*_pRefCount) == 0) {
D()(_ptr); // 创建匿名对象,并调()重载
delete _pRefCount;
deleteflag = true;
}
_pMutex.unlock();
if (deleteflag == true)
delete _pMutex;
}
}

使用方式:
SharedPtr<int> np(new int);
SharedPtr<FILE, Close<FILE>> fp(fopen("./test","r"));
SharedPtr<int, Free<int>> mp((int*)malloc(sizeof(int)));

shared_ptr的循环引用

shared_ptr并非完美,也有小缺陷

比如:

1
2
3
4
5
6
7
8
9
10
11
12
struct ListNode {
int _data;
shared_ptr<ListNode> _prev;
shared_ptr<ListNode> _next;
~ListNode() { cout << "~ListNode()" << endl; }
};
void test() {
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
node1->_next = node2;
node2->_prev = node1;
} // 这里会导致内存泄漏

img1
出作用域后node1和node2对象被释放
img2
但是引用计数不为0,_date不会被释放,造成内存泄漏

当然,C++11库中也给出了响应的解决方法,就是weak_ptr

weak_ptr

weak_ptr要和shared_ptr搭配使用,在进行如上的赋值时,并不进行引用计数的加加操作,这也保证了在释放的时候不会因为引用计数不为0而没有正确释放,造成内存泄漏

1
2
3
4
5
6
struct ListNode {
int _data;
weak_ptr<ListNode> _prev;
weak_ptr<ListNode> _next;
~ListNode() { cout << "~ListNode()" << endl; }
};

C++11 守卫锁

lock_guard利用RAII思想,通过对象的生命周期控制锁的生命周期构造加锁,析构解锁,防止死锁

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
template<class Mutex>
class LockGuard {
public:
LockGuard(Mutex& mtx)
:_mutex(mtx) {
_mutex.lock();
}
~LockGuard() {
_mutex.unlock();
}
LockGuard(const LockGuard<Mutex>&) = delete;
private:
// 必须使用引用,保证同一个互斥量对象
Mutex& _mutex;
};

void Fuc() {
mutex mtx;
// 进入作用域构造lg对象,加锁
{
LockGuard<mutex> lg(mtx);
// ...code
// ...code
// ...code
}
// 出了作用域析构lg对象,解锁
}