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; // 锁
};


目录
相关文章
|
1月前
|
存储 程序员 C++
深入解析C++中的函数指针与`typedef`的妙用
本文深入解析了C++中的函数指针及其与`typedef`的结合使用。通过图示和代码示例,详细介绍了函数指针的基本概念、声明和使用方法,并展示了如何利用`typedef`简化复杂的函数指针声明,提升代码的可读性和可维护性。
80 0
|
2月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
195 4
|
3月前
|
存储 安全 编译器
在 C++中,引用和指针的区别
在C++中,引用和指针都是用于间接访问对象的工具,但它们有显著区别。引用是对象的别名,必须在定义时初始化且不可重新绑定;指针是一个变量,可以指向不同对象,也可为空。引用更安全,指针更灵活。
|
3月前
|
存储 C++
c++的指针完整教程
本文提供了一个全面的C++指针教程,包括指针的声明与初始化、访问指针指向的值、指针运算、指针与函数的关系、动态内存分配,以及不同类型指针(如一级指针、二级指针、整型指针、字符指针、数组指针、函数指针、成员指针、void指针)的介绍,还提到了不同位数机器上指针大小的差异。
100 1
|
3月前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
60 2
|
3月前
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
3月前
|
存储 C++ 索引
C++函数指针详解
【10月更文挑战第3天】本文介绍了C++中的函数指针概念、定义与应用。函数指针是一种指向函数的特殊指针,其类型取决于函数的返回值与参数类型。定义函数指针需指定返回类型和参数列表,如 `int (*funcPtr)(int, int);`。通过赋值函数名给指针,即可调用该函数,支持两种调用格式:`(*funcPtr)(参数)` 和 `funcPtr(参数)`。函数指针还可作为参数传递给其他函数,增强程序灵活性。此外,也可创建函数指针数组,存储多个函数指针。
113 6
|
4月前
|
编译器 C++
【C++核心】指针和引用案例详解
这篇文章详细讲解了C++中指针和引用的概念、使用场景和操作技巧,包括指针的定义、指针与数组、指针与函数的关系,以及引用的基本使用、注意事项和作为函数参数和返回值的用法。
67 3
|
3月前
|
存储 编译器 程序员
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(二)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
4月前
|
C++
C++(十八)Smart Pointer 智能指针简介
智能指针是C++中用于管理动态分配内存的一种机制,通过自动释放不再使用的内存来防止内存泄漏。`auto_ptr`是早期的一种实现,但已被`shared_ptr`和`weak_ptr`取代。这些智能指针基于RAII(Resource Acquisition Is Initialization)原则,即资源获取即初始化。RAII确保对象在其生命周期结束时自动释放资源。通过重载`*`和`-&gt;`运算符,可以方便地访问和操作智能指针所指向的对象。