Fork me on GitHub

Design your own shared_ptr

Motivation

​ In recent days, I just dabbed into the area of resource management in C++, and after going through the tiresome recycle the resource using manual delete each time when we finished the functionality. I came acrross the magical attribution of the local variable———it can recyle the resource at some certain occassions, so I utilized which to free me from a lot of meaningless “delete” code(Through writing delete instruction do helpe me gain the whole picture of the program).

​ Just two weeks later, I talked with one of my friends about my (so-called) best practice of resource management. He just told me the fact that what I did is just a minor subset that the smart pointer published in C++11. Then I decided to take some notes in it, just to be a little closer to the goal mastering the C++(doge). After a quick browsering of the textbook& blog& manual provided on the Internet, I have a rough idea of what is it and how to use it, but without practicing in the real industry project. I still feel very uneasy about the detail of it, like the sentences that is haunting around my mind: “what I cannot create, I do not understand.“ So I decided to create my our wheel, maybe ugly, but which is so meaningful to my career.

Devil is in the details

​ In the rest of the passage, I will introduce a naive smart implementation of smart pointer.

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
60
61
62
63
64
65
#include <iostream>
#include <memory>

template<typename T>
class NaiveSmartPointer {
private:
T* _ptr;
size_t* _count;
public:
NaiveSmartPointer(T* ptr = nullptr) : _ptr(ptr) {
if (_ptr) {
_count = new size_t(1);
}else {
_count = new size_t(0);
}
}

NaiveSmartPointer(SmartPointer& ptr) {
if (this != &ptr) {
this->_ptr = ptr._ptr;
this->_count = ptr._count;
++(*this->_count);
}
}

NaiveSmartPointer& operator=(const SmartPointer &ptr) {
if (this->_ptr == ptr._ptr) return *this;

if (this->_ptr) {
--(*this->_count);
if (*this->_count == 0) {
//Recycle the pointed object;
delete this->_ptr;
delete this->_count;
}
}

this->_ptr = ptr._ptr;
this->_count = ptr._count;
++(*this->_count);
return *this;
}

T& operator*() {
assert(this->_ptr == nullptr);
return *(this->_ptr);
}

T* operator->() {
assert(this->_ptr == nullptr);
return this->_ptr;
}

~SmartPointer () {
--(*this->_count);
if (*this->_count == 0) {
delete this->_ptr;
delete this->_count;
}
}

size_t use_count() {
return *this->_count;
}
}

​ The whole picture seems very great, but if we are more careful, we can detect some issues:

​ 1.Since the increment and decrement of reference count requires atomicy, the high frequency may slow down the performance in the concurrent circumstances.

​ 2.The latent leakage of memory will occur in some special cases, since the relationship between smart pointer and targeted object is tight-binded, so the cycle reference will be a huge disaster to the whole system. We have am example below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<memory>

class B;
class A {
public:
std::shared_ptr<B> m_b;
}

class B {
public:
std::shared_ptr<A> m_a;
}

int main() {
while(true) {
std::shared_ptr<A> a(new A); //Initialize A's reference count(1)
std::shared_ptr<B> b(new B); //Initialize B's reference count(1)

a->m_b = b; //Increase B's reference count(2)
b->m_a = a; //Increase A's reference count(2)
}
//When the variable a and b are out of scope, we should decrease the reference count of targeted oject, so the final rc of A and B is 1, which will not recyle the space of object A and B.
}

​ In this scenario, we need to introduce the weak_ptr. The weak_ptr will not modify the target object’s rc.

​ The next wheel to create must be the unique_ptr(In fact it is much more easy to implement than 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
template<typename T>
class unique_ptr {
private:
T* _ptr;
public:
unique_ptr(T& t) {
_ptr = &t;
}

unique_ptr(unique_ptr<T>&& uptr) {
_ptr = std::move(uptr._ptr);
uptr._ptr = nullptr;
}

~unique_ptr() {
delete _ptr;
}
unique_ptr<T>& operator=(unique_ptr<T>&& uptr) {
if (this == uptr) return *this;
_ptr = std::move(uptr._ptr);
uptr._ptr = nullptr;
return *this;
}
}