C++中智能指针和野指针

参考文章:

C++中智能指针的设计和使用

C++智能指针及其简单实现

C++智能指针 shared_ptr,unique_ptr和weak_ptr

【C++进阶】C++中的空指针和野指针

一. 简介

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++中指针有++操作,因而在执行该操作的时候,稍有不慎,就容易指针访问越界,访问了一个不该访问的内存,结果程序崩溃。
另一种情况是指针指向一个临时变量的引用,当该变量被释放时,此时的指针就变成了一个野指针。

猜你喜欢

转载自blog.csdn.net/weixin_39731083/article/details/81534333