二、智能指针#

1. 指针潜在问题#

c++ 把内存的控制权对程序员开放,让程序显式的控制内存,这样能够快速的定位到占用的内存,完成释放的工作。但是此举经常会引发一些问题,比如忘记释放内存。由于内存没有得到及时的回收、重复利用,所以在一些c++程序中,常会遇到程序突然退出、占用内存越来越多,最后不得不选择重启来恢复。造成这些现象的原因可以归纳为下面几种情况.

1. 野指针#

出现野指针的有几个地方 :

a. 指针声明而未初始化,此时指针的将会随机指向

b. 内存已经被释放、但是指针仍然指向它。这时内存有可能被系统重新分配给程序使用,从而会导致无法估计的错误

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

using namespace std;

int mian(){


    //1. 声明未初始化
    int *p1 ;
    cout << "打印p1: " << *p1 << endl;


    //2. 内存释放后,并没有置空 nullptr
    int *p = new int(55);

    cout << "释放前打印 :  " << *p << endl;

    delete  p ;
    cout << "释放后打印 :  " << *p << endl;

    return 0 ;
}

2. 重复释放#

程序试图释放已经释放过的内存,或者释放已经被重新分配过的内存,就会导致重复释放错误.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int main(){

    int *p = new int(4);

    //重复释放
    delete p;
    delete p;

    return 0 ;
}

3. 内存泄漏#

不再使用的内存,并没有释放,或者忘记释放,导致内存没有得到回收利用。 忘记调用delete

1
2
3
4
5
6
7
8
9
int main(){

    int *p = new int(4);


    //后面忘记调用delete p;

    return 0 ;
}

2. 智能指针#

为了解决普通指针的隐患问题,c++在98版本开始追加了智能指针的概念,并在后续的11版本中得到了提升。

在98版本提供的auto_ptr 在 c++11得到删除,原因是拷贝是返回左值、不能调用delete[] 等。 c++11标准改用 unique_ptr | shared_ptr | weak_ptr 等指针来自动回收堆中分配的内存。智能指针的用法和原始指针用法一样,只是它多了些释放回收的机制罢了。

智能指针位于` 头文件中,所以要想使用智能指针,还需要导入这个头文件#include`

1. unique_ptr#

unique_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
#include<iostream>
#include <memory>

using namespace std;

int main(){


    //1. 创建unique_ptr对象,包装一个int类型指针
    unique_ptr<int> p(new int(10));  

    //2. 无法进行拷贝。编译错误
    //unique_ptr<int> p2 = p; 
    cout << *p << endl; 

    //3. 可以移动指针到p3. 则p不再拥有指针的控制权 p3 现在是唯一指针
    unique_ptr<int> p3 = move(p) ; 
    cout << *p3 << endl;

    //p 现在已经无法取值了。
    cout << *p << endl;

    //可以使用reset显式释放内存。
    p3.reset(); 

    //重新绑定新的指针
    p3.reset(new int(6));

    //获取到曾经包装的int类型指针
    int *p4 = p3.get() ; 

    //输出6
    cout << "指针指向的值是:" << *p4  << endl; 

    return 0 ;    
}

2. shared_ptr#

shared_ptr : 允许多个智能指针共享同一块内存,由于并不是唯一指针,所以为了保证最后的释放回收,采用了计数处理,每一次的指向计数 + 1 , 每一次的reset会导致计数 -1 ,直到最终为0 ,内存才会最终被释放掉。 可以使用use_cout 来查看目前的指针个数

 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
#include <iostream>
#include <memory>
using namespace std;

class stu{

public:
    stu(){
        cout << "执行构造函数" <<endl;
    }

    ~stu(){
        cout << "执行析构函数" <<endl;
    }

};

int main(){

    shared_ptr<stu> s1 ( new stu());

   cout <<" cout = " << s1.use_count() <<endl; //查看指向计数

    shared_ptr<stu> s2  = s1;

    s1.reset();
    s2.reset();  // 至此全部解除指向 计数为0 。 会执行stu的析构函数

    return 0 ;
}

3. shared_ptr的问题#

对于引用计数法实现的计数,总是避免不了循环引用(或环形引用)的问题,即我中有你,你中有我,shared_ptr也不例外。 下面的例子就是,这是因为f和s内部的智能指针互相指向了对方,导致自己的引用计数一直为1,所以没有进行析构,这就造成了内存泄漏。

 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
class father {
public:
    father(){cout <<"father 构造" << endl;}
    ~father(){cout <<"father 析构" << endl;}

    void setSon(shared_ptr<son> s) {
        son = s;
    }
private:
    shared_ptr<son> son;
};

class son {
public:
    son(){cout <<"son 构造" << endl;}
    ~son(){cout <<"son 析构" << endl;}
    void setFather(shared_ptr<father> f) {
        father = f;
    }
private:
    shared_ptr<father> father;
};


int main(){

    shared_ptr<father> f(new father());
    shared_ptr<son> s(new son());
    f->setSon(s);
    s->setFather(f);
}

4. weak_ptr#

为了避免shared_ptr的环形引用问题,需要引入一个弱指针weak_ptr,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。不论是否有weak_ptr指向,一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。从这个角度看,weak_ptr更像是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
class father {
public:
    father(){cout <<"father 构造" << endl;}
    ~father(){cout <<"father 析构" << endl;}
    void setSon(shared_ptr<son> s) {
        son = s;
    }
private:
    shared_ptr<son> son;
};

class son {
public:
    son(){cout <<"son 构造" << endl;}
    ~son(){cout <<"son 析构" << endl;}
    void setFather(shared_ptr<father> f) {
        father = f;
    }
private:
    //shared_ptr<father> father; 
    weak_ptr<father> father;  //替换成weak_ptr 即可。
};


int main(){

    shared_ptr<father> f(new father());
    shared_ptr<son> s(new son());
    f->setSon(s);
    s->setFather(f);
}