C++智能指针的简单实现,原理及应用

发布时间:2023年12月28日

1. 为什么C++引入了智能指针?

在C++中,引入智能指针主要是为了解决原始指针在使用过程中可能出现的内存泄漏问题。内存泄漏是程序在申请内存后,无法释放已分配的内存,导致内存被无效占用,严重时可能导致系统运行缓慢甚至崩溃。

原始指针在使用过程中,如果忘记释放内存,就会导致内存泄漏。为了避免这个问题,C++引入了智能指针。智能指针是一个能够自动管理内存的指针,它会在离开作用域时自动释放内存,从而避免了内存泄漏的问题。

2.智能指针的分类

智能指针主要有以下几种:

  1. std::unique_ptr:独占所有权的智能指针,只能有一个unique_ptr指向某个对象,当unique_ptr被销毁时,它所指向的对象也会被销毁。
  2. std::shared_ptr:共享所有权的智能指针,允许多个shared_ptr指向同一个对象,当最后一个shared_ptr被销毁时,它所指向的对象才会被销毁。
  3. std::weak_ptr:从shared_ptr派生的智能指针,可以与shared_ptr共享所有权,但它不能单独使用,必须配合shared_ptr使用。

通过使用智能指针,可以有效地避免内存泄漏问题,提高程序的稳定性和可靠性

3.简单实现demo用于理解共享指针

我就用在实战中用得最多的共享指针来做个例子,在扩展到其他两个智能指针

一般这种需要用到类模板来适应不同的类型

template <class T>

用到三个成员指针对象,

    int* m_count;       //计数
    T* m_ptr;           //指向对象
    std::mutex* m_mutex;//锁
   int* m_count;       //计数,用于计数,计数值到零时释放所有对象
   T* m_ptr;           //指向对象
   std::mutex* m_mutex;//锁,用于多线程下的线程安全

提示:面试时如果你说你会C++智能指针,有时候面试官会问你原理是什么,或者你有深入了解过智能指针的源码嘛,实现原理,你能说出来,是个加分项,共享指针的重点在于有一个计数器,理解它怎么计数的很重要

比如我定义了一个类如下

template <class T>
class SharedPointer
{
????????typedef SharedPointer<T> self;
};

1. 自定义构造函数,拷贝函数,赋值函数来赋值,在拷贝和赋值函数中让计数器+1

    explicit SharedPointer(T* ptr=nullptr):m_count(new int(1)),m_ptr(ptr){
        m_mutex = new std::mutex;
    }    
    SharedPointer(const self& other) {
        m_count = other.m_count;
        m_ptr = other.m_ptr;
        m_mutex = other.m_mutex;
        addCount();
    }
    SharedPointer& operator=(const self& other){
        if(m_ptr != other.m_ptr) {
            m_count = other.m_count;
            m_ptr = other.m_ptr;
            m_mutex = other.m_mutex;
            addCount();
        }
        return *this;
    }

需要重载一些运算符来操作原有对象

    T* Get()const { return m_ptr; }
    T& operator* () const { return *m_ptr; }
    T* operator->() const { return m_ptr; }
    operator bool() const { return m_ptr; }

还有一些其他函数,其中release函数用来减少计数器的,放在析构函数中调用,当计数器count为0时,自动回收资源

    int getCount(){
        m_mutex->lock();
        int count = *m_count;
        m_mutex->unlock();
        return count;
    }
private:
    void addCount(){
        m_mutex->lock();
        (*m_count)++;
        m_mutex->unlock();
    }
    void release(){
        m_mutex->lock();
        if(--(*m_count)==0) {
            delete m_ptr;
            delete m_mutex;
            delete m_count;
            qDebug()<<"释放所有资源。。。";
        }
        m_mutex->unlock();
    }

完整代码如下:

#ifndef SHAREDPOINTER_H
#define SHAREDPOINTER_H
#include <mutex>
#include <QtDebug>
/*
在C++中,类模板(class template)和类模板成员函数的定义通常需要放在头文件中。
这是因为编译器在编译时需要看到完整的模板定义,以便实例化模板生成相应的代码。
如果模板的定义放在源文件中,那么在每个使用该模板的源文件中都需要包含该头文件,
否则编译器将无法识别模板。因此,为了简化代码的维护和提高编译效率,通常将模板的定义放在头文件中。
*/

template <class T>
class SharedPointer
{
    typedef SharedPointer<T> self;
public:
    explicit SharedPointer(T* ptr=nullptr):m_count(new int(1)),m_ptr(ptr){
        m_mutex = new std::mutex;
    }
    explicit SharedPointer(){}
    ~SharedPointer(){   release(); }
    SharedPointer(const self& other) {
        m_count = other.m_count;
        m_ptr = other.m_ptr;
        m_mutex = other.m_mutex;
        addCount();
    }
    SharedPointer& operator=(const self& other){
        if(m_ptr != other.m_ptr) {
            m_count = other.m_count;
            m_ptr = other.m_ptr;
            m_mutex = other.m_mutex;
            addCount();
        }
        return *this;
    }
    T* Get()const { return m_ptr; }
    T& operator* () const { return *m_ptr; }
    T* operator->() const { return m_ptr; }
    operator bool() const { return m_ptr; }

    int getCount(){
        m_mutex->lock();
        int count = *m_count;
        m_mutex->unlock();
        return count;
    }
private:
    void addCount(){
        m_mutex->lock();
        (*m_count)++;
        m_mutex->unlock();
    }
    void release(){
        m_mutex->lock();
        if(--(*m_count)==0) {
            delete m_ptr;
            delete m_mutex;
            delete m_count;
            qDebug()<<"释放所有资源。。。";
        }
        m_mutex->unlock();
    }
private:
    int* m_count;       //计数
    T* m_ptr;           //指向对象
    std::mutex* m_mutex;//锁

};
#endif // SHAREDPOINTER_H

写一段测试程序:

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    {
        SharedPointer<test> T1 =  SharedPointer<test>(new test(1));

        qDebug()<<"T1.Count"<< T1.getCount();
        {
            SharedPointer<test> T2 = T1;
            qDebug()<<"T1.Count"<<T1.getCount();
        }
        qDebug()<<"T1.Count"<<T1.getCount();
        {
            SharedPointer<test> T3 =  T1;
            qDebug()<<"T1.Count"<<T1.getCount();
        }
        qDebug()<<"T1.Count"<<T1.getCount();
    }


    return a.exec();
}

输出如下:

可以看到,当共享指针的对象,每赋值一次,计数+1,离开作用域时,生命周期结束,会调用析构函数,计数-1,放计数=0时,自动释放所有资源

共享指针的原理说完了,在讲讲其他两种指针

4. 其他两种指针

1.弱指针

1.弱指针用来解决共享指针的循环引用的问题,弱指针是共享指针的辅助类,允许共享但不拥有某对象,不会关联对象的引用次数。
2.不能使用运算符*和->直接访问弱指针的引用对象,而是通过Lock函数生成对象的共享指针(可能为空)

3.当拥有该对象的最后一个共享指针失去其所有权时,任何弱指针都会自动变为空

4.可以强转化为强指针,即共享指针

循环引用的例子:

class Node {  
public:  
    int data;  
    std::shared_ptr<Node> next;  
  
    Node(int data) : data(data), next(nullptr) {}  
};  
  
int main() {  
    std::shared_ptr<Node> head = std::make_shared<Node>(1);  
    std::shared_ptr<Node> second = std::make_shared<Node>(2);  
    std::shared_ptr<Node> third = std::make_shared<Node>(3);  
  
    head->next = second;  
    second->next = third;  
    third->next = head; // 形成循环引用  
  
    // 由于存在循环引用,head、second、third都无法被正确释放  
    // 内存泄漏发生  
  
    return 0;  
}

弱指针解决共享指针的循环引用:

class Node {  
public:  
    int data;  
    std::weak_ptr<Node> next;  
  
    Node(int data) : data(data), next(nullptr) {}  
};  
  
int main() {  
    std::shared_ptr<Node> head = std::make_shared<Node>(1);  
    std::shared_ptr<Node> second = std::make_shared<Node>(2);  
    std::shared_ptr<Node> third = std::make_shared<Node>(3);  
  
    head->next = second;  
    second->next = third;  
    third->next = head; // 形成循环引用  
  
    // 使用weak_ptr打破循环引用  
    head = nullptr; // 释放head指针,使其成为弱引用  
    second = nullptr; // 释放second指针,使其成为弱引用  
    third = nullptr; // 释放third指针,使其成为弱引用  
  
    // 现在head、second、third可以被正确释放,内存泄漏得到解决  
  
    return 0;  
}

2. 独占指针

顾名思义,独占式拥有, 确保一个对象和其相应的资源同一时间只被一个 pointer拥有,意味着他没有拷贝构造函数,赋值函数,保证其唯一性

5. 总结

项目中用到最多的就是共享指针了,特别是一个指针对象被多次引用,导致释放时无从下手或者很麻烦时,就要用共享指针来避免内存泄露了,其他两种指针用的较少,用起来也不难,理解就行了

文章来源:https://blog.csdn.net/qq_44667165/article/details/135265538
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。