参考文章:
C++智能指针 shared_ptr,unique_ptr和weak_ptr
一. 简介
1. 为什么要用智能指针?
①忘记释放导致内存泄漏
int main(){
int *ptr = new int(0);
return 0;
}
②产生野指针:程序虽然最后释放了申请的内存,但ptr会变成空悬指针(dangling pointer,也就是野指针)。空悬指针不同于空指针(nullptr),它会指向“垃圾”内存,给程序带去诸多隐患。
int main(){
int *ptr = new int(0);
delete ptr;
return 0;
}
③由异常引起的内存泄漏:如下,当我们的程序运行到“if(hasException())”处且“hasException()”为真,那程序将会抛出一个异常,最终导致程序终止,而已申请的内存并没有释放掉。
#include <iostream>
using namespace std;
int main()
{
int *ptr = new(nothrow) int(0);
if(!ptr)
{
cout << "new fails."
return 0;
}
// 假定hasException函数原型是 bool hasException()
if (hasException())
throw exception();
delete ptr;
ptr = nullptr;
return 0;
}
2. 什么是智能指针?
- 智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露。
- 它的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。
每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,减少引用计数(如果引用计数减至0,则删除基础对象)。 - 智能指针就是模拟指针动作的类。所有的智能指针都会重载 -> 和 * 操作符。智能指针还有许多其他功能,比较有用的是自动销毁。这主要是利用栈对象的有限作用域以及临时对象(有限作用域实现)析构函数释放内存。当然,智能指针还不止这些,还包括复制时可以修改源对象等。智能指针根据需求不同,设计也不同(写时复制,赋值即释放对象拥有权限、引用计数等,控制权转移等)。
3.C++有哪些智能指针类?
目前C++标准库的智能指针主要有这3类:
指针 | 简要描述 |
---|---|
shared_ptr | 允许多个指针指向同一个对象,该对象和其相关资源会在“最后一个引用(reference)被销毁”时候释放。 |
unique_ptr | 独占所指向的对象,保证同一时间内只有一个智能指针可以指向该对象。 |
weak_ptr | shared_ptr的弱引用 |
二. 智能指针的使用
1. shared_ptr
- shared_ptr是一个标准的共享所有权的智能指针,就是允许多个指针指向同一对象,shared_ptr对象中不仅有一个指针指向某某(比如 int型,以下也拿int类型举例)对象,还拥有一个引用计数器,代表一共有多少指针指向了那个对象。
每当创建一个shared_ptr的对象指向int型数据,则引用计数器值+1,每当销毁一个shared_ptr对象,则-1.当引用计数器数据为0时,shared_ptr的析构函数会销毁int型对象,并释放它占用的内存。 - shared_ptr和new的配合使用
shared_ptr<int> p1; //被初始化成为一个空指针 shared_ptr<int> p2 (new int(4)); //指向一个值是4的int类型数据 shared_ptr<int> p3 = new int(4); //错误,必须直接初始化
- 不能混合使用普通指针和智能指针,因为智能指针不是单纯的赤裸裸的指针
void process(shared_ptr<int> ptr){ //受到参数值传递的影响,ptr被构造并且诞生,执行完函数块后被释放 } int *x(new int (43)); //x是一个普通的指针 process(x); //错误,int * 不能转换成shared_ptr<int>类型 process(shared_ptr<int> x); //临时创造了x,引用数+1,执行完process之后,引用数-1 int j = *x; //x是一个空悬指针,是未定义的
- shared_ptr的一些操作
shared_ptr<T> p; //空智能指针,可指向类型是T的对象 if(p) //如果p指向一个对象,则是true (*p) //解引用获取指针所指向的对象 p -> number == (*p).number; p.get(); //返回p中保存的指针 swap(p,q); //交换p q指针 p.swap(q); //交换p,q指针 make_shared<T>(args) //返回一个shared_ptr的对象,指向一个动态类型分配为T的对象,用args初始化这个T对象 shared_ptr<T> p(q) //p 是q的拷贝,q的计数器++,这个的使用前提是q的类型能够转化成是T* shared_pts<T> p(q,d) //p是q的拷贝,p将用可调用对象d代替delete //上面这个我其实没懂,也没有查出来这个的意思 p =q; //p的引用计数-1,q的+1,p为零释放所管理的内存 p.unique(); //判断引用计数是否是1,是,返回true p.use_count(); //返回和p共享对象的智能指针数量 p.reset(); p.reset(q); p.reset(q,d); //reset()没懂,这个以后再来补充吧
2.unique_ptr
- 与shared_ptr不同,某一时刻,只能有一个unique_ptr指向一个给定的对象。因此,当unique_ptr被销毁,它所指的对象也会被销毁。
- unique_ptr的初始化必须采用直接初始化
unique_ptr<string> p(new string("China")); //没问题 unique_ptr<string> p (q); //错误,不支持拷贝 unique_ptr<string> q; q = p; //错误,不支持赋值
- unique_ptr的一些操作:
unique_ptr<T> p; //空智能指针,可指向类型是T的对象 if(p) //如果p指向一个对象,则是true (*p) //解引用获取指针所指向的对象 p -> number == (*p).number; p.get(); //返回p中保存的指针 swap(p,q); //交换p q指针 p.swap(q); //交换p,q指针 unique_ptr<T,D>p; //p使用D类型的可调用对象释放它的指针 p = nullptr; //释放对象,将p置空 p.release(); //p放弃对指针的控制,返回指针,p置数空 p.reset(); //释放p指向的对象 p.reset(q); //让u指向内置指针q
3.weak_ptr
- weak_ptr是一种不控制所指向对象生存期的智能指针,指向shared_ptr管理的对象,但是不影响shared_ptr的引用计数。它像shared_ptr的助手,一旦最后一个shared_ptr被销毁,对象就被释放,weak_ptr不影响这个过程。
- weak_ptr的一些操作:
weak_ptr<T> w(sp); //定义一个和shared_ptr sp指向相同对象的weak_ptr w,T必须能转化成sp指向的类型 w = p; //p是shared_ptr或者weak_ptr,w和p共享对象 w.reset(); //w置为空 w.use_count(); //计算与w共享对象的shared_ptr个数 w.expired(); //w.use_count()为0,返回true w.lock(); //w.expired()为true,返回空shared_ptr,否则返回w指向对象的shared_ptr
三. 野指针
1. 什么是野指针?
野指针和空指针不一样,是一个指向垃圾内存的指针。
2. 为什么会产生野指针?
①指针变量没有被初始化:
任何指针变量被刚创建时不会被自动初始化为NULL指针,它的缺省值是随机的。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
②指针被free或者delete之后,没有设置为NULL,让人误以为这是一个合法指针:
free和delete只是把指针所指向的内存给释放掉,但并没有把指针本身给清理掉。这时候的指针依然指向原来的位置,只不过这个位置的内存数据已经被毁尸灭迹,此时的这个指针指向的内存就是一个垃圾内存。但是此时的指针由于并不是一个NULL指针(在没有置为NULL的前提下)。
③指针操作超越了变量的作用范围:
由于C/C++中指针有++操作,因而在执行该操作的时候,稍有不慎,就容易指针访问越界,访问了一个不该访问的内存,结果程序崩溃。
另一种情况是指针指向一个临时变量的引用,当该变量被释放时,此时的指针就变成了一个野指针。