C++智能指针

简介: C++智能指针

为什么需要智能指针

首先来分析一段代码

int div()
{
    int a, b;
    cin >> a >> b;
    if (b == 0)
      throw invalid_argument("除0错误");
    return a / b;
}
void Func()
{
    int* p1 = new int;
    int* p2 = new int;
    cout << div() << endl;
    delete p1;
    delete p2;
}
int main()
{
    try
    {
        Func();
    }
    catch (exception& e)
    {
        cout << e.what() << endl;
    }
  return 0;
}

根据异常的性质可以得知,如果发生了异常抛出异常后,throw下面的代码就不会执行了。那么上面的代码问题在哪里呢,因为new本身就是会调用底层的函数,那么new本身就有可能会出现异常。如果new异常了那下面的释放空间就不会执行了,这样就导致了内存泄露的问题。因此为了这种的情况的避免,就引入了智能指针的概念。

什么是智能指针

在了解什么是智能指针前先来了解一个简单的技术

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术

因为在C++中一个类对象在释放后会自动地调用类的析构函数,所以可以利用这个性质将指向开辟好空间的指针作为构造参数放到一个类中,这样该类中的指针成员指向的空间就和传入的指针的地址是一样的,将空间的释放放到类的析构中,这样即使异常抛出,只要对象销毁所开辟的空间也会自动销毁。这就是实现智能指针的基本思想

那么根据这种思想可以来对上述的代码进行一个改造

template<class T>
class SmartPtr
{
public:
    SmartPtr(T* p = nullptr) : _p(p)
    {}
    ~SmartPtr()
    {
        if(_p)
          delete _p;
    }
private:
    T* _p;
};
int div()
{
    int a, b;
    cin >> a >> b;
    if (b == 0)
      throw invalid_argument("除0错误");
    return a / b;
}
void Func()
{
    SmartPtr<int> p1(new int);
  SmartPtr<int> p2(new int);
    cout << div() << endl;
}
int main()
{
    try
    {
        Func();
    }
    catch (exception& e)
    {
        cout << e.what() << endl;
    }
  return 0;
}

这样即使new p2的参数时抛出异常也不会影响到p1的释放。

auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针,就是上述代码的思想,不过上述代码并没有实现出指针的用法,而智能指针是可以跟指针一样用的。那么就可以给上述代码的类进一步改造就是模拟C++98库中的auto_ptr了

template<class T>
class Auto_ptr
{
public:
    Auto_ptr(T* ptr)
      :_ptr(ptr)
    {}
    Auto_ptr(auto_ptr<T>& sp)
      :_ptr(sp._ptr)
    {
        // 管理权转移
        sp._ptr = nullptr;
    }
    Auto_ptr<T>& operator=(auto_ptr<T>& ap)
    {
        // 检测是否为自己给自己赋值
        if (this != &ap)
        {
            // 释放当前对象中资源
            if (_ptr)
              delete _ptr;
            // 转移ap中资源到当前对象中
            _ptr = ap._ptr;
            ap._ptr = NULL;
        }
        return *this;
    }
    ~Auto_ptr()
    {
        if (_ptr)
        {
            cout << "delete:" << _ptr << endl;
            delete _ptr;
        }
    }
    // 像指针一样使用
    // 实现解引用和取地址
    T& operator*()
    {
      return *_ptr;
    }
    T* operator->()
    {
      return _ptr;
    }
private:
  T* _ptr;
};

但是这种设计是有很大问题的:资源的管理权转移意味该对象不能在对原来管理的资源进行访问了,如果进行访问,会导致程序崩溃,很容易出现问题,因此绝大多数的公司都是明确要求不能使用auto_ptr的

unique_ptr

C++11中就出现了更为靠谱的unique_ptr,这个实现的原理就是简单粗暴的禁止拷贝。

template<class T>
class Unique_ptr
{
public:
    Unique_ptr(T* ptr)
      :_ptr(ptr)
    {}
    ~Unique_ptr()
    {
        if (_ptr)
        {
            cout << "delete:" << _ptr << endl;
            delete _ptr;
        }
    }
    // 像指针一样使用
    T& operator*()
    {
      return *_ptr;
    }
    T* operator->()
    {
      return _ptr;
    }
    // 禁止拷贝
    Unique_ptr(const unique_ptr<T>& sp) = delete;
    Unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
private:
  T* _ptr;
};

但是直接防止拷贝也并不是很好,这样子的应用场景就很受限

shared_ptr

shared_ptr就再次改进,更靠谱也支持拷贝

这个的原理就是:

通过引用计数的方式来实现多个shared_ptr对象之间共享资源,shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减1。如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

那么用什么计数呢,首先肯定不能使用内置成员变量计数,因为每一个对象都会有属于自己独立的成员变量。其次也不能使用静态成员变量,如果是静态成员变量,那么是所有类型对象共享的,这会导致管理相同资源的对象和管理不同资源的对象都是用同一个引用计数

真正的解决办法是利用指针,让所有对象都能看到同一个地址,利用该地址空间存储计数,因为这个计数是属于临界资源,为了保证安全可以利用加锁来保证

template<class T>
class Shared_ptr
{
public:
    Shared_ptr(T* ptr = nullptr)
        :_ptr(ptr)
        , _pRefCount(new int(1))
        , _pmtx(new mutex)
    {}
    Shared_ptr(const shared_ptr<T>& sp)
        :_ptr(sp._ptr)
        , _pRefCount(sp._pRefCount)
        , _pmtx(sp._pmtx)
    {
      AddRef();
    }
    // 计数减1
    void Release()
    {
        _pmtx->lock();
        bool flag = false;
        if (--(*_pRefCount) == 0 && _ptr)
        {
            cout << "delete:" << _ptr << endl;
            delete _ptr;
            delete _pRefCount;
            flag = true;
      }
      _pmtx->unlock();
        if (flag == true)
          delete _pmtx;
    }
    // 计数加1
    void AddRef()
    {
        _pmtx->lock();
        ++(*_pRefCount);
        _pmtx->unlock();
    }
    Shared_ptr<T>& operator=(const shared_ptr<T>& sp)
    {
        if (_ptr != sp._ptr)
        {
            Release();
            _ptr = sp._ptr;
            _pRefCount = sp._pRefCount;
            _pmtx = sp._pmtx;
            AddRef();
        }
        return *this;
    }
    // 返回当前有多少个对象共享
    int use_count()
    {
      return *_pRefCount;
    }
    ~Shared_ptr()
    {
      Release();
    }
    // 像指针一样使用
    T& operator*()
    {
      return *_ptr;
    }
    T* operator->()
    {
      return _ptr;
    }
    T* get() const
    {
      return _ptr;
    }
private:
    T* _ptr;
    int* _pRefCount; // 计数
    mutex* _pmtx; // 锁
};


目录
相关文章
|
3天前
|
编译器 C++
【C++核心】指针和引用案例详解
这篇文章详细讲解了C++中指针和引用的概念、使用场景和操作技巧,包括指针的定义、指针与数组、指针与函数的关系,以及引用的基本使用、注意事项和作为函数参数和返回值的用法。
11 3
|
24天前
|
C++
C++(十八)Smart Pointer 智能指针简介
智能指针是C++中用于管理动态分配内存的一种机制,通过自动释放不再使用的内存来防止内存泄漏。`auto_ptr`是早期的一种实现,但已被`shared_ptr`和`weak_ptr`取代。这些智能指针基于RAII(Resource Acquisition Is Initialization)原则,即资源获取即初始化。RAII确保对象在其生命周期结束时自动释放资源。通过重载`*`和`-&gt;`运算符,可以方便地访问和操作智能指针所指向的对象。
|
24天前
|
C++
C++(九)this指针
`this`指针是系统在创建对象时默认生成的,用于指向当前对象,便于使用。其特性包括:指向当前对象,适用于所有成员函数但不适用于初始化列表;作为隐含参数传递,不影响对象大小;类型为`ClassName* const`,指向不可变。`this`的作用在于避免参数与成员变量重名,并支持多重串联调用。例如,在`Stu`类中,通过`this-&gt;name`和`this-&gt;age`明确区分局部变量与成员变量,同时支持链式调用如`s.growUp().growUp()`。
|
1月前
|
存储 安全 C++
C++:指针引用普通变量适用场景
指针和引用都是C++提供的强大工具,它们在不同的场景下发挥着不可或缺的作用。了解两者的特点及适用场景,可以帮助开发者编写出更加高效、可读性更强的代码。在实际开发中,合理选择使用指针或引用是提高编程技巧的关键。
24 1
|
1月前
|
安全 NoSQL Redis
C++新特性-智能指针
C++新特性-智能指针
|
1月前
|
编译器 C++
virtual类的使用方法问题之在C++中获取对象的vptr(虚拟表指针)如何解决
virtual类的使用方法问题之在C++中获取对象的vptr(虚拟表指针)如何解决
|
1月前
|
存储 安全 编译器
C++入门 | auto关键字、范围for、指针空值nullptr
C++入门 | auto关键字、范围for、指针空值nullptr
51 4
|
2月前
|
存储 安全 C++
浅析C++的指针与引用
虽然指针和引用在C++中都用于间接数据访问,但它们各自拥有独特的特性和应用场景。选择使用指针还是引用,主要取决于程序的具体需求,如是否需要动态内存管理,是否希望变量可以重新指向其他对象等。理解这二者的区别,将有助于开发高效、安全的C++程序。
28 2
|
1月前
|
存储 C++
c++学习笔记06 指针
C++指针的详细学习笔记06,涵盖了指针的定义、使用、内存占用、空指针和野指针的概念,以及指针与数组、函数的关系和使用技巧。
30 0
|
1月前
|
安全 编译器 容器
C++STL容器和智能指针
C++STL容器和智能指针