原反补移码

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

C++中的智能指针

一、为什么需要智能指针?

  在c++中内存管理中,有一个需要程序员手动分配与释放的内存空间叫做堆。我们可以使用new 运算符来生成一个动态对象并为其在在堆上分配空间。由于堆得空间大小是有限的,当我们不再使用动态对象时,需要程序员手动释放该内存空间。

  new,在动态内存中(堆)为对象分配空间并返回一个指向该对象的指针,可以选择对对象初始化。

  delete, 接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。

  动态内存的管理是很复杂的,很容易造成内存泄漏以及引用非法内存的指针。所以标准库提供了两种智能指针,来方便对动态内存的使用。

二、shared_ptr

  2.1 shared_ptr的定义与引用计数的内涵

  从shared可以看出该类型智能指针允许多个指针指向同一对象。

  下面是默认初始化的智能指针的定义形式,没有初始值的智能指针默认初始化为空。

1 shared_ptr<int> s1;
2 shared_ptr<vector<int>> s2;

  可以使用make_shared<type>(初始值)这种形式来对shared_ptr进行初始化。

shared_ptr<int> s1 = make_shared<int>(); //动态对象初始化为0 注意这种只有()的初始化方式
shared_ptr<string> s2 = make_shared<string>("Test"); // 动态对象初始化为“Test”

  shared_ptr既然是可以允许多个指针指向同一对象。那么就会有它所指那个对象的引用计数(代表有多少指针指向了该动态对象,一旦引用计数为0时,该动态对象才会被销毁)

  下面这段代码可以体现这一点,代码的结果如图所示。指针s1指向一个动态分配的对象,然后在定义一个s2并利用s1初始化。那么use_count()的含义是该智能指针所指向的动态对象有多少引用(通俗一点就是:多少指针指向该动态对象

 1 #include<iostream>
 2 #include<memory>
 3 using namespace std;
 4 int main(void) {
 5     shared_ptr<int> s1 = make_shared<int>(10);
 6     cout << "s1.use_count() = " << s1.use_count() << endl;
 7     shared_ptr<int> s2 = s1;
 8     cout << "s1.use_count() = " << s1.use_count() << endl;
 9     cout << "s2.use_count() = "<< s2.use_count() << endl;
10     getchar();
11     return 0;
12 }
View Code

 

  同理,当一个shared_ptr指针被重新赋值,那么它之前所指的动态对象的引用计数减一,而赋值给它的那个指针所指的动态对象引用计数加一。 

 1 #include<iostream>
 2 #include<memory>
 3 
 4 using namespace std;
 5 
 6 int main(void) {
 7     shared_ptr<int> s1 = make_shared<int>(10);
 8     shared_ptr<int> s2 = s1;
 9     cout << "s1.use_count() = " << s1.use_count() << endl;
10     cout << "s2.use_count() = " << s2.use_count() << endl;
11     shared_ptr<int> s3 = make_shared<int>(9);
12     s1 = s3; //对s1重新赋值
13     cout << "s1.use_count() = " << s1.use_count() << endl; 
14     cout << "s2.use_count() = "<< s2.use_count() << endl;
15     cout << "s3.use_count() = " << s3.use_count() << endl;
16     getchar();
17     return 0;
18 }

  当s1重新赋值,s1和s3引用计数相同,且s2引用计数减去1.因为s2和s1之前指向相同的动态对象。

  当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象(通过一个特殊的成员函数--析构函数)。

  2.2  shared_ptr与new运算符

  利用new返回的指针来初始化shared_ptr

shared_ptr<int> s1 = new int(2); //错误
shared_ptr<int> s2(new int(2));  //直接初始化方式正确

  因为接受指针参数的智能指针构造函数是explicit的,因此不能讲一个内置指针隐式转换为一个智能指针,必须使用直接初始化。

   2.3 智能指针与普通指针混用以及shared_ptr.get()

  在上面我们提到可以用new返回的运算符给shared_ptr初始化,但是在实际使用过程中最好不要这么做。如下代码:

 1 #include<iostream>
 2 #include<memory>
 3 using namespace std;
 4 int main(void) {
 5     int *p1 = new int(3);
 6     shared_ptr<int> s1(p1);
 7     cout << "s1.use_count() = " << s1.use_count() << endl; //虽然s1和p1指向相同的内存,但是引用计数只有1.
 8     s1.reset();
 9     cout << "s1.use_count() = " << s1.use_count() << endl; //释放了s1所指向的内存,这样p1指向就是非法内存
10     cout << *p1 << endl;                                   //所以*p1的值就是未定义的。
11     getchar();
12     return 0;
13 }

  从上面的例子可以看出,shared_ptr的引用计数含义是:指向该动态分配的内存的智能指针个数(非智能指针不包括在内),一旦引用计数为0,则该动态分配的内存就会释放掉,无论是否有其他普通指针指向该动态内存。

  我们再来看一下shared_ptr的get()方法,该方法返回一个普通指针(和它所属的智能指针指向同一块内存空间),先看一段代码结果:

#include<iostream>
#include<memory>
using namespace std;

int main(void) {
    shared_ptr<int> s1 = make_shared<int>(3);
    shared_ptr<int> s2(s1.get());
    cout << "s1.use_count() = " << s1.use_count() << endl;  //s1和s2引用计数都为1.尽管是指向同一内存空间。
    cout << "s2.use_count() = " << s2.use_count() << endl; //因为系统认为这是独立创建的两个智能指针,尽管指向同一内存。
    s1.reset();                           //释放s1指向的内存,那么s2指向的就变成了了非法内存。但是s2的引用计数不会为0.
    cout << "s1.use_count() = " << s1.use_count() << endl;  
    cout << "s2.use_count() = " << s2.use_count() << endl;
    cout << *s2 << endl;                     //非法内存取值,是一个未定义的值。
    getchar();
    return 0;
}

  从上面结果可以看出, 由于s1和s2是相互独立创建的,所以会认为是两个没有关系的智能指针,尽管指向同一内存空间。

三、unique_ptr

  3.1 初始化

 1 #include<iostream>
 2 #include<memory>
 3 using namespace std;
 4 
 5 int main(void) {
 6     unique_ptr<int> u1;  //默认为空指针
 7     unique_ptr<int> u2(new int(3)); //最普遍的定义方式
 8     unique_ptr<int> u3(u2.get());  //尽管指向同一内存,但是是不同的智能指针,逻辑上还是一个指针指向一个“特定的”内存空间。
 9     
10     //错误的定义与赋值(这是允许的)
11     unique_ptr<int> u4 = new int(4); //错误,不支持拷贝初始化
12     unique_ptr<int> u5 = u4;    //错误,不支持拷贝
13     unique_ptr<int> u6(u5);    //错误,也不支持这种形式直接初始化
14     unique_ptr<int> u7 = make_shared<int>(5);  //错误,只允许直接初始化,且make_shared方式不能用于unique_ptr的定义。
15     getchar();
16     return 0;
17 }

  unique_ptr初始化规则

  • 只支持直接初始化形式或不初始化(为nullptr)
  • 不支持需要调用拷贝构造函数的初始化形式
  • 不支持赋值

  3.2 传递unique_ptr参数和返回unique_ptr

  上面提到,不能赋值和拷贝。但是作为函数参数或返回是可以的。即可以拷贝或赋值一个将要被销毁的unique_ptr.只要保证任意时刻只有一个unique_ptr指向同一内存空间(u_pt.get()这种除外)

unique_ptr<int>clone(int p){
    return unique_ptr<int>(new int(p));
}

unique_ptr<int>clone(int p){
    unique_ptr<int> ret (new int(p);
    return ret;        
}

  3.3 release() and reset()

  release()用于释放对指针的控制权,本指针置空。且返回一个指针。

 1 #include<iostream>
 2 #include<memory>
 3 using namespace std;
 4 
 5 int main(void) {
 6     unique_ptr<int> u1(new int(3));
 7     unique_ptr<int> u2(u1.release());   //u1置空,且返回所指向的指针给u2。
 8     cout << *u2 << endl;  //u2接管了u1所指向的对象。输出3
 9     u1.reset(new int(4));  //u1重置,为一个new新对象
10     cout << *u2 << endl;
11     u2.reset();
12     if (u2 == nullptr)
13         cout << "u2 == nullptr" << endl;
14     else
15         cout << "u2 != nullptr" << endl;
16     getchar();
17     return 0;
18 }

  我们可以用reset重置指针,可以重置为为空,也可以为new出来的对象,也可以为release().

四、weak_ptr

  weak_ptr是一种不控制所指向对象生命期的智能指针,它指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被销毁。无论是否weak_ptr指向该对象。

 1 #include<iostream>
 2 #include<memory>
 3 using namespace std;
 4 
 5 int main(void) {
 6     shared_ptr<int> s1(new int(3));
 7     weak_ptr<int> w1 = s1;
 8     weak_ptr<int> w2 = w1;
 9     cout << "s1.use_count() = " << s1.use_count() << endl;  //w1 和 w2 不会增加s1的引用计数,但三种相等 = 1。
10     cout << "w1.use_count() = " << w1.use_count() << endl;
11     cout << "w2.use_count() = " << w2.use_count() << endl;
12     weak_ptr<int> w3 = make_shared<int>(3); 
13     cout << "w2.use_count() = " << w2.use_count() << endl;
14     //shared_ptr<int> s2 = w2;   //错误,并不能从weak_ptr to shared_ptr
15     w3.reset();  //w3 == nullptr
16     if (w3.expired())  //判断w3.use_count()是否为0
17         cout << "w3.use_count() == 0" << endl;
18     else
19         cout << "w3.use_count() != 0" << endl;
20     weak_ptr<int> w4 = w3.lock();   //返回w3所指向的对象,若不存在则返回nullptr
21     weak_ptr<int> w5 = w2.lock();
22     if (w4.expired())
23         cout << "w4 == nullptr" << endl;
24     else
25         cout << "w4 != nullptr" << endl;
26     if (w5.expired())
27         cout << "w5 == nullptr" << endl;
28     else
29         cout << "w5 != nullptr" << endl;
30     getchar();
31     return 0;
32 }
posted on 2020-04-05 20:43  原反补移码  阅读(106)  评论(1)    收藏  举报