智能指针基础原理

智能指针和普通指针的区别在于智能指针实际上是对普通指针加了一层封装机制,这样的一层封装机制的目的是为了使得智能指针可以方便的管理一个对象的生命期,实现内存的自我回收

对于普通的 局部变量(非静态局部变量),当离开它的作用域时,操作系统会自动将其释放。类对象在释放的时候是会自动调用该类的析构函数。

于是我们就想:如果是Test *t不是一个普通的指针变量,而是一个类对象的话,并且在类的析构函数中实现了释放动态内存的步骤,那么只要该指针变量一退出作用域时就会调用析构函数,达到了释放动态内存的目的。这也就是智能指针的基本思想。

根据设想,在上篇文章最后一个示例程序的基础上可以自己实现一个最简易的智能指针:

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

class Test
{
public:
Test(int x = 0) :m_val(x) {};
~Test(){cout << "~Test() has been called" << endl;}

int m_val;
};

class SmartPointer
{
public:
SmartPointer(Test* p) { ptr = p; }
~SmartPointer()
{
delete ptr;
cout << "~SmartPointer() has been called" << endl;
}

Test* ptr;
};

int main(void)
{
try
{
SmartPointer t(new Test(100));
cout << "t->m_val" << t.ptr->m_val << endl;
throw("throw exception");//人为抛出异常
}// t作用域离开时,自动析构d,elete
catch (...)// 捕获所有异常
{
cout << "something has gone wrong!" << endl;
}

return 0;
}

运行结果:

1
2
3
4
5
t->m_val100
~Test() has been called
~SmartPointer() has been called
something has gone wrong!
请按任意键继续. . .

C++11智能指针

四种智能指针

C++ STL为我们提供了四种智能指针:

  • auto_ptr( C++98提供,C++11建议弃用)
    • 主要是用来解决最基本的资源自动释放问题
  • unique_ptr
    • 可视作auto_ptr的替代品
    • 持有对象的独有权,同一时刻只能有一个unique_ptr指向给定对象
  • shared_ptr
    • 允许多个该智能指针共享堆中分配的内存(指向同一对象),通过引用计数实现管理
    • 一旦最后一个这样的指针被销毁(计数变为0),该对象会被自动删除
  • weak_ptr
    • 一般与shared_ptr配合使用,它可以从shared_ptr构造,其构造和析构不改变引用计数
    • 没有重载-> * 操作符,不能直接使用资源,需通过lock函数取回一个shared_ptr对象
    • 作用是解决shared_ptr的循环引用问题,避免递归的依赖关系

基本使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <string>
#include <memory> //智能指针定义文件
using namespace std;

int main(void)
{
auto_ptr<string> pa(new string("using auto ptr"));
cout << "pa-size:" << pa->size() << endl;

unique_ptr<string> pu(new string("using unique ptr"));
cout << "pu-size:" << pu->size() << endl;

shared_ptr<string> ps(new string("using shared ptr"));
cout << "ps-size:" << ps->size() << endl;

cout << "*pa:" << *pa << endl;

return 0;
}

运行结果:

1
2
3
4
5
pa-size:14
pu-size:16
ps-size:16
*pa:using auto ptr
请按任意键继续. . .

auto_ptr存在的问题

先来看如下代码:

1
2
3
auto_ptr<int> px(new int(8));
auto_ptr<int> py;
py = px;

上述赋值语句将两个指针指向同一内存地址,在析构时可能会被两个对象各自delete一次,而同一块内存是不能delete两次的。避免这种问题,主要有以下两种方法:

  • 建立所有权概念。对于特定对象,同一时刻只能有一个智能指针可拥有, 最终只有拥有对象的智能指针的构造函数会删除该对象,auto_ptrunique_ptr就是采用这种策略
  • 创建智能更高的指针,跟踪引用特定对象的智能指针个数,进行引用计数。shared_ptr采用这种策略

通过代码测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <string>
#include <memory> //智能指针定义文件
using namespace std;

int main(void)
{
auto_ptr<int> px(new int(10));
auto_ptr<int> py;
cout << "[1] *px = " << *px << endl;
py = px;
cout << "[2] *px = " << *px << endl;// 将px赋值给py后再来访问px

return 0;
}

运行结果(只输出[1] *px = 10并抛出异常):

1
Expression : auto_ptr not dereferencable

翻译过来是“表达式:auto_ptr不可撤销引用”?

单步调试可发现,当执行完赋值语句后,px的地址为empty,也就是空指针NULL,再去访问px当然会出错了。

如果将上述代码中的auto_ptr换为unique_ptr,在程序编译时就会提示错误,因而它可以在编译时将潜在的错误暴露出来:

1
error C2280: 'std::unique_ptr<int,std::default_delete<_Ty>> &std::unique_ptr<_Ty,std::default_delete<_Ty>>::operator =(const std::unique_ptr<_Ty,std::default_delete<_Ty>> &)': attempting to reference a deleted function

如果将上述代码中的auto_ptr换为shared_ptr,程序可以正常运行:

1
2
3
[1] *px = 10
[2] *px = 10
请按任意键继续. . .

因为shared_ptr采用引用计数,当执行完赋值语句py = px后,pxpy都指向同一块内存,只不过在释放空间时因为事先要判断引用计数值的大小,因此不会出现多次删除一个对象的错误。

选择使用参考

  • 如果程序中要使用多个指向同一个对象的指针,那么应该使用shared_ptr

    比如说现在有一个包含指针的STL容器,现在用某个支持复制和赋值操作的STL算法去操作该容器的指针元素,那么就应该用shared_ptr。不能用auto_ptr(行为不确定)或unique_ptr(编译时报错)。

  • 如果程序中不需要使用多个指向同一个对象的指针,则可使用unique_ptr

    如果函数使用new分配内存,并返回指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。

  • 在使用环境不支持C++11时,使用auto_ptr

  • weak_ptr可以避免auto_ptr的递归依赖关系

参考:

【C++札记】C++智能指针

https://www.cnblogs.com/lsgxeva/p/7788061.html

https://blog.csdn.net/zsc_976529378/article/details/52250597