# C++手写智能指针

# 如何实现一个”线程安全“的shared_ptr智能指针?

这个题目拿到手后得搞清楚shared_ptr的原理 1.在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。 2.在对象被销毁时,就说明自己不使用该资源了,对象的引用计数减一。 3.如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源; 4.如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

而且题目既然要求线程安全,就得考虑下面两个问题: 1.智能指针对象中的引用技术是多个对象所共享的,两个线程同时对智能指针进行++或–运算,这个计数原来是1,++了两次可能还是2,这样引用技术就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、–是需要加锁的,也就是说引用计数的操作是线程安全的。 2.智能指针的对象存放在堆上,可能会导致线程安全问题。

实现一个线程安全的shared_ptr智能指针需要满足以下要求:

  1. 任何时刻只能有一个线程能够修改计数器和指针。
  2. 当一个线程正在修改计数器和指针时,其他线程不能够读取或修改计数器和指针。
  3. 当一个线程在使用shared_ptr时,其他线程不能够销毁指向的对象。
#include<iostream>
#include<mutex>

using namespace std;

//引用计数器
class mycounter{
public:
    mycounter(): m_used(0){}
    mycounter( const mycounter& )=delete;
    ~mycounter(){}
    mycounter& operator=(const mycounter&)=delete;

    void reset(){
        m_used=0;
    }
    unsigned int get() const{
        return m_used;
    }

    void operator++(){
        ++m_used;
    }
    void operator++(int){
        m_used++;
    }
    void operator--(){
        --m_used;
    }
    void operator--(int){
        m_used--;
    }
    
    friend ostream& operator<<( ostream& os , const mycounter& temp){
        os<< "counter value=" << temp.m_used <<endl;
        return os;
    }

private:
    unsigned int m_used;
};


//核心智能指针
template< typename T >
class myshared_ptr{
public:
    explicit myshared_ptr( T *ptr=nullptr ): m_ptr(ptr), 
    m_pCounter(new mycounter()), m_pMutex(new mutex){
        if( nullptr!=ptr ){
            add_ref_count(); //添加引用计数
        }
    }
    myshared_ptr( myshared_ptr<T> &sp );
    ~ myshared_ptr();

    myshared_ptr<T> & operator=( const myshared_ptr<T> &sp );
    T * operator->();
    T & operator*();
    T * get();
    unsigned int usecount();

private:
    T *m_ptr;
    mutex * m_pMutex; //互斥锁
    mycounter *m_pCounter;

    void add_ref_count();
    void realse();
};

//实现细节
template<typename T>
myshared_ptr<T>::myshared_ptr(myshared_ptr<T> &sp){

    //?浅拷贝✅
    m_ptr=sp.m_ptr;
    m_pCounter=sp.m_pCounter;
    m_pMutex=sp.m_pMutex;
    add_ref_count();
}

template< typename T>
myshared_ptr<T>& 
myshared_ptr<T>::operator=(const myshared_ptr<T>& sp){
    //防止自己assign给自己✅
    if(m_ptr != sp.m_ptr){
        realse(); //为什么呢?✅✅因为自身原来指向的内存引用要减1

        //?浅拷贝
        m_ptr=sp.m_ptr;
        m_pCounter=sp.m_pCounter;
        m_pMutex=sp.m_pMutex;

        add_ref_count();
    }
  	
  	//如果是自己拷贝给自己
  	return *this;
}

template< typename T>
T *
myshared_ptr<T>::operator->(){
    return m_ptr; //因为会自动去获得
}

template< typename T>
T &
myshared_ptr<T>::operator*(){
    return *m_ptr;
}

template< typename T>
T *
myshared_ptr<T>::get(){
    return m_ptr;
}

template< typename T>
unsigned int
myshared_ptr<T>::usecount(){
    return m_pCounter->get(); //使用class的
}

template< typename T>
myshared_ptr<T>::~myshared_ptr(){
    realse();
}

template< typename T>
void
myshared_ptr<T>::add_ref_count(){
    m_pMutex->lock(); //加锁
    ++(*m_pCounter); //增加引用
    m_pMutex->unlock(); //解锁
}

template< typename T>
void
myshared_ptr<T>::realse(){
  
    //如果已经消亡了,那么不要realse了
    if( 0==m_pCounter->get() ){
      return ;
    }
  
    bool deleteflag=false;
    m_pMutex->lock();

    --(*m_pCounter); //先-1
    //如果引用计数为0,那么就消亡了
    if( 0==m_pCounter->get() ){
        delete m_pCounter;
        delete m_ptr;
        deleteflag = true;
    }

    m_pMutex->unlock();
    if( deleteflag ){
        delete m_pMutex; //此时才需要把互斥锁消除
        //m_pMutex=nullptr;
    }
      
}



int main(){

    return 0;
}
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172

# 旷视-实现一个简单的智能指针

template<typename T>
class SharedPointer {
private:
     T* _ptr;
     size_t* _count;
public:
     SharedPointer(T* ptr = nullptr) {
       //补充代码
     }

    SharedPointer& operator=(const SharedPointer& smptr) {
        //补充代码
    }

    ~SharedPointer() {
        //补充代码
    }
};


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template<typename T>
class SharedPointer {
private:
     T* _ptr;
     size_t* _count;
public:
     SharedPointer(T* ptr = nullptr) {
        (*_count)=1;
        _ptr=ptr;
     }

    SharedPointer& operator=(const SharedPointer& smptr) {
        if( *this==smptr ) return *this; 

        if( _ptr==smptr._ptr ){
            return *this;
        }
        else{
            (*this)::~SharedPointer();
            _ptr=smptr._ptr;
            smptr.increace_ptr();//
        }
        
        return *this;
    }

    ~SharedPointer() {
        --(*_count);
        if( 0==(*_count) ){
            delete _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

# 实现一个unique_ptr,并且讲一下其RAII

先说结论,智能指针都是非线程安全的。

  • 学这个,一定要会move语句,std::move

  • 一旦拥有着被销毁或编程 empty,或开始拥有另一个对象,先前拥有的那个对象就会被销毁,其任何相应资源亦会被释放。

  • 参考资料:https://www.jianshu.com/p/77c2988be336

# 实现单例

class Singleton{
private: //私有的
    Singleton();
    Singleton(const Singleton& other);
public:
    static Singleton* getInstance();
    static Singleton* m_instance;
    static pthread_mutex_t m_lock;
};
Singleton* Singleton::m_instance=nullptr;
1
2
3
4
5
6
7
8
9
10
//懒汉式
//线程非安全版本
Singleton* Singleton::getInstance() {
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}
1
2
3
4
5
6
7
8
//懒汉式子
//线程安全版本,但锁的代价过高
Singleton* Singleton::getInstance() {
    Lock lock; //加上锁
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
  
  	//至此加锁结束
    return m_instance;
}
1
2
3
4
5
6
7
8
9
10
11
//懒汉式子
//线程安全版本,但锁的代价过高
Singleton* Singleton::getInstance() {
  	pthread_mutex_lock( &m_lock );
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
  	pthread_mutex_unlock( &m_lock);
  
  	//至此加锁结束
    return m_instance;
}
1
2
3
4
5
6
7
8
9
10
11
12
//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {
    
    if(m_instance==nullptr){ //如果空,才加锁,如果不空,不用加锁的!
        Lock lock; //???
        if (m_instance == nullptr) {
            m_instance = new Singleton();
        }
      //锁结束
    }
    return m_instance;
}

1
2
3
4
5
6
7
8
9
10
11
12
13

看起来很完美,但是其实还是有问题:

相当一段时间,迷惑了很多人,包括很多专家。最后一个java领域的专家知道了漏洞:

由于内存读写reorder不安全

什么叫reorder?会导致双检查锁的失效!

常见假设:

先构造内存,然后new,然后把地址给

CPU层面,指令级别!reorder意思是重新排序:

(编译器优化会导致:)先分配内存,把地址给

reorder优化是,编译器干的!

第一时间,解决方法,java和C#加了个关键字,叫volatile

需要对static Singleton* m_instance;加上一个volatile,这样,编译器在编译器的时候,过程不能reorder的。是要按照常规

那C++呢?

VC++2005版本自家加上了volatile关键词,不是跨平台的。但是C++追求跨平台的,直到C++11后,才能有机制来实现跨平台的实现,如下:

# C++11的

//C++ 11版本之后的跨平台实现 (volatile)
std::atomic<Singleton*> Singleton::m_instance; //声明一个原子对象
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
    Singleton* tmp = m_instance.load(std::memory_order_relaxed);//这个可以帮助我们屏蔽编译器的reorder
    std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
  
    if (tmp == nullptr) {
      
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = m_instance.load(std::memory_order_relaxed); //取变量的时候,以这种方式来取
        if (tmp == nullptr) {
            tmp = new Singleton;
            std::atomic_thread_fence(std::memory_order_release);//释放内存fence
            m_instance.store(tmp, std::memory_order_relaxed);
        }
      
    }
    return tmp;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//C++ 11版本之后的跨平台实现 (volatile)
atomic<Singleton*> Singleton::m_instance; //声明一个原子对象
mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
  
    Singleton* tmp = m_instance.load(memory_order_relaxed);//这个可以帮助我们屏蔽编译器的reorder
    atomic_thread_fence(memory_order_acquire);//获取内存fence
  
    if (tmp == nullptr) {
      
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = m_instance.load(memory_order_relaxed); //取变量的时候,以这种方式来取
      
        if (tmp == nullptr) {
            tmp = new Singleton;
          
            std::atomic_thread_fence(std::memory_order_release);//释放内存fence
            m_instance.store(tmp, memory_order_relaxed);
        }
      
    }
    return tmp;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24