智能指针

一、两种智能指针

智能指针是C++11标准引入的一种管理动态内存的工具,用于自动管理动态分配的内存,
避免内存泄漏和悬空指针问题。C++11标准提供了三种智能指针:
unique_ptr、shared_ptr和weak_ptr。

shared_ptr允许多个指针指向同一个对象。

unique_ptr独占所指向的对象,尽量使用unique_ptr,资源占用少,效率更高。

weak_ptr是一种弱引用,指向shared_ptr所管理的对象,但不增加引用计数。

二、共享智能指针 shared_ptr

1、是什么:

shared_ptr共享它指向的对象,多个shared_ptr可以指向同一个对象。
在内部采用技术机制实现。

当新的shared_ptr指向一个对象时,引用计数会增加1;当shared_ptr销毁时,引用计数会减少1。
当计数变为0时该对象的shared_ptr被销毁时,该对象会被自动删除。

2、初始化方法:

  • 方法一:使用new关键字,分配内存并初始化

    shared_ptr <类名> 指针名(new 类名(参数));

    shared_ptr<A> p1(new A(10));
    
  • 方法二:使用make_shared函数,C++11标准

    shared_ptr <类名> 指针名 = make_shared<类名>(参数);

    shared_ptr<A> p2 = make_shared<A>(10);
    
  • 方法三:用已存在的地址

    A *p = new A(10);
    shared_ptr <A> sp(p);
    
  • 方法四:拷贝构造和赋值

    shared_ptr<A> p(new A(10));
    shared_ptr<A> p1 = p;       //计数器+1
    shared_ptr<A> p2(p);        //计数器+1
    

3、使用方法

  • 智能指针重载了 * 和 -> 操作符,可以直接使用指针访问对象。

  • 计数器的值: use_count()

  • 支持普通的拷贝和赋值,左值的shared_ptr的计数器会 +1,右值的shared_ptr的计数器会 -1。

  • unique()方法,如果use_count()返回1,则返回true,否则返回false。

  • get()方法,返回原始指针。

    #include<iostream>
    #include<memory>
    
    using namespace std;
    
    class A
    {
    public:
        A()
        {
            std::cout << "A()" << std::endl;
        }
    
        ~A()
        {
            std::cout << "~A()" << std::endl;
        }
    
        A(const int a):_a(a)
        {
            cout << "a = " << a << endl;
        }
    
        int _a;
        
    };
    
    int main()
    {
        //方法一,使用new初始化
        shared_ptr<A> p1(new A(10));
        shared_ptr<A> p7 = p1;
    
        //方法二,使用make_shared初始化
        shared_ptr<A> p2 = make_shared<A>(20);
    
        //方法三,使用已存在的指针初始化
        A *p3 = new A(30);
        shared_ptr<A> p4(p3);
    
        //方法四,拷贝构造和赋值
        shared_ptr<A> p5 = p4;
        shared_ptr<A> p6(p5);
    
        cout << "之前p1: " << p1.use_count() << endl;   //2
    
        p7 = p4;
    
        cout << "之后p1: " << p1.use_count() << endl;   
        //此处减少了一个指向p1的指针,所以变为1
    
        cout << "之后p1的unique()方法:" << p1.unique() << endl;
        //1,true
    
        cout << "p4: " << p4.use_count() << endl;       
        //下面都指向同一个指针,所以计数器都为4
    
        cout << "p4的unique()方法:" << p4.unique() << endl;
        //0,false
        cout << "p5: " << p5.use_count() << endl;       //4
    
        cout << "p6: " << p6.use_count() << endl;       //4
        return 0;
    }
    
  • 不要用同一个裸指针初始化多个智能指针,否则会导致重复删除同一块内存的问题。

    #include <iostream>
    #include <memory>
    
    class A 
    {
    public:
        A() { std::cout << "A constructor" << std::endl; }
        ~A() { std::cout << "A destructor" << std::endl; }
    };
    
    int main() 
    {
        A* rawPtr = new A(); // 裸指针
    
        std::shared_ptr<A> ptr1(rawPtr); 
        // 第一个 shared_ptr
        std::shared_ptr<A> ptr2(rawPtr); 
        //第二个 shared_ptr,使用同一个裸指针初始化
    
        //当 ptr1 和 ptr2 超出作用域时,它们的析构函数都会被调用,
        // 从而导致对同一块内存(rawPtr 指向的内存)进行两次删除操作。
    
        return 0;
    }
    

    原因如下:
    ptr1 和 ptr2 都用同一个裸指针 rawPtr 来初始化。由于 shared_ptr 的引用计数机制是基于构造函数来增加引用计数的,而这里有两个 shared_ptr 实例指向同一块内存,但它们的引用计数是独立的(因为它们是用同一个裸指针分别构造的,而不是通过复制或移动构造函数),所以每个 shared_ptr 在析构时都会认为自己是最后一个指向该内存的 shared_ptr,并尝试删除它。这就会导致未定义行为,通常是程序崩溃。
    正确做法:

    shared_ptr<A> ptr1 = make_shared<A>();
    shared_ptr<A> ptr2 = ptr1; 
    // 现在 ptr2 是 ptr1 的一个副本,它们共享同一个对象和控制块
    
  • 不要用shared_ptr管理不是new分配的内存,否则会导致内存泄漏的问题。

三、独占智能指针 unique_ptr

有一个简单类:

class A
{
private:
    int _a;
public:
    A()
    {
        cout << "A()" << endl;
    }
    A(const int a):_a(a)
    {
        cout << "A(int)" << endl;
    }
    ~A()
    {
        cout << "~A()" << endl;
    }

};

1、使用方法

  • 方法一:使用new关键字,分配内存并初始化

    unique_ptr <类名> 指针名(new 类名(参数));

    unique_ptr<A> p1(new A(10));
    
  • 方法二:使用make_unique函数,C++14标准

    unique_ptr <类名> 指针名 = make_unique<类名>(参数);

    unique_ptr<A> p2 = make_unique<A>(10);
    
  • 方法三:用已存在的地址(此方法可能出错,易错

    类名 *p = new 类名(参数);

    unique_ptr <类名> 指针名(p);

    A *p = new A(10);
    unique_ptr<A> p3(p);
    

2、注意

  • 不支持拷贝构造函数和赋值操作符,因为unique_ptr独占所指向的对象。

    A *p = new A(10);
    unique_ptr<A> p1 = p;       //错误,不支持赋值操作;
    unique_ptr<A> p2 = new A(10);    //错误,不能将普通指针赋值给智能指针
    
    unique_ptr<A> p3(new A(10));
    unique_ptr<A> p4 = p3;    //错误,不支持拷贝构造函数;
    
    unique_ptr<A> p5;
    p5 = p3;                  //错误,不支持赋值操作;
    
  • 不支持与shared_ptr相互转换,
    因为shared_ptr允许多个指针指向同一个对象,而unique_ptr独占所指向的对象。

  • 不要用同一个指针初始化多个unique_ptr,否则会报错。

        shared_ptr<A> ptr1 = make_shared<A>(10);
        shared_ptr<A> ptr2 = ptr1; // 现在 ptr1 和 ptr2 共享同一个 A 对象
    
        cout << "ptr1: " << *ptr1 << endl;
        cout << "ptr2: " << *ptr2 << endl;
    
        // 当 ptr1 和 ptr2 都超出作用域时,A 对象才会被删除
    
  • 不要用unique_ptr管理不是new分配的内存,否则会报错。

  • get方法返回裸指针,显示原始指针的地址。

    A *p = new A(10);
    unique_ptr<A> up(p); 
    // 注意:这里不需要 p,因为 unique_ptr 会自己管理内存。
    // 更好的做法是直接使用 make_unique:unique_ptr<A> up = make_unique<A>(10);
    
    cout << "裸指针的地址:" << p << endl;
    // 注意:此时 p 已经是悬空指针,不建议使用!
    // 由于 up 接管了 p 的内存,p 不再有效。上面的输出仅用于说明,实际应避免。
    
    cout << "智能指针的地址:" << up << endl;
    cout << "up.get():" << up.get() << endl;
    cout << "up的地址:" << &up << endl;
    
    // 不需要 delete p,因为 unique_ptr 会自动处理。
    
    return 0;
    

3、作为参数传递

当unique_ptr作为参数传递给函数时,应该传递unique_ptr的引用,不能进行值传递,因为unique_ptr不支持拷贝构造函数和赋值操作符。

    class A
    {
    public:
        A()
        {
            cout << "A()" << endl;
        }
        int _a;
    };

    void fun(unique_ptr<A> p)
    {
        cout << p->_a << endl;
    }

    int main()
    {
        unique_ptr<A> p(new A(10));
        fun(p);
        return 0;
    }

unique_ptr 被设计为独占的智能指针。一旦 unique_ptr 拥有某个资源(在这里是一个 A 类型的对象),
它就不允许其他 unique_ptr 或任何形式的复制来共享这个资源。
这是通过删除 unique_ptr 的拷贝构造函数和赋值操作符来实现的。

当unique_ptr< A > 作为值参数传递给函数 fun 时,
编译器会尝试调用被删除的复制构造函数来创建一个新的 unique_ptr< A > 实例作为函数参数。
这就是为什么会看到一个编译错误,指出正在使用一个被删除的函数。

四、weak_ptr

五、智能指针删除器

在默认情况下,智能指针要过期时,会自动调用delete来释放内存。
但有时,我们可能需要使用其他方式来释放内存,比如调用自定义的析构函数或者使用其他类型的内存管理器。
这时,我们可以使用智能指针的删除器来实现。

删除器可以是全局函数lambda表达式或者仿函数。智能指针在过期时会调用删除器来释放内存。

1、shared_ptr的删除器

#include<iostream>
#include<memory>

using namespace std;

class A
{
public:
    A()
    {
        std::cout << "A()" << std::endl;
    }

    ~A()
    {
        std::cout << "~A()" << std::endl;
    }

    A(const int a):_a(a)
    {
        cout << "a = " << a << endl;
    }

    int _a;

};

void deletefunc(A *a)
{
    cout << "普通函数删除器" << endl;
    //此处可以不delete a,但是后果自负,不会调用析构函数
    delete a;
}

struct deleteclass
{
    void operator()(A *a)
    {
        cout << "仿函数删除器" << endl;
        delete a;
    }
};

auto deletelamb = [](A *a)
{
    cout << "lambda表达式删除器" << endl;
    delete a;
};
    

int main()
{
    //shared_ptr<A> p1(new A(10));        //使用缺省的删除器

    //普通函数做删除器
    shared_ptr<A> p2(new A(20), deletefunc);

    //仿函数删除器
    shared_ptr<A> p3(new A(30), deleteclass());

    //lambda表达式做删除器
    shared_ptr<A> p4(new A(40), deletelamb);

    return 0;
}

2、unique_ptr的删除器

unique_ptr的删除器

    unique_ptr<A,decltype(deletefunc)*> p5(new A(10), deletefunc);

    unique_ptr<A, deleteclass> p6(new A(20), deleteclass());

    unique_ptr<A, decltype(deletelamb)> p7(new A(30), deletelamb);

六、weak_ptr

1、shared_ptr存在的问题

shared_ptr内部维护了一个引用计数,当引用计数为0时,shared_ptr会自动释放所管理的对象。
当出现循环引用时,引用计数永远不会为0,导致内存泄漏。

#include<iostream>
#include<memory>

using namespace std;

class B;

class A
{
public:
    A()
    {
        cout << "A()" << endl;
    }

    ~A()
    {
        cout << "~A()" << endl;
    }

    shared_ptr<B> pb;
};

class B
{
public:
    B()
    {
        cout << "B()" << endl;
    }

    ~B()
    {
        cout << "~B()" << endl;
    }

    shared_ptr<A> pa;
};

int main()
{
    shared_ptr<A> pa(new A());
    shared_ptr<B> pb(new B());
    //不会调用析构函数

    pa->pb = pb;
    pb->pa = pa;

    cout << "pa.use_count() = " << pa.use_count() << endl; // 输出 2
}

2、weak_ptr的引入

weak_ptr是为了配合shared_ptr引入的,它指向一个由shared_ptr管理的资源但不影响资源的生命周期。weak_ptr不会增加引用计数,因此不会阻止shared_ptr释放所管理的对象。

weak_ptr可以用来解决循环引用的问题,它可以与shared_ptr相互转换,但是不能直接访问所管理的对象,需要通过shared_ptr来访问。

//其他均不变,将A、B类中的shared_ptr改为weak_ptr
weak_ptr<B> pb;
weak_ptr<A> pa;

3、weak_ptr的使用

weak_ptr不控制对象的生命周期,但是它知道对象是否或者。

weak_ptr没有重载*和->运算符,因此不能直接访问所管理的对象。

weak_ptr的成员函数

  • operator=();//将shared_ptr或weak_ptr赋值给weak_ptr

  • expired(); //判断weak_ptr所管理的对象是否已经释放,返回true表示已经释放,返回false表示未释放

  • lock(); //将weak_ptr转换为shared_ptr,如果weak_ptr所管理的对象已经释放,则返回一个空的shared_ptr,这个行为是线程安全的。

  • reset(); //将weak_ptr重置为空

  • swap(); //交换两个weak_ptr所管理的对象

错误方式:

if(pa->pb.expired()) == true
    cout << "pa->pb已经释放" << endl;
else
    cout << "pa->pb.lock() << pa->pb.lock()->_a" << endl;

正确方式:

shared_ptr<B> temp = pa->pb.lock();
//将weak_ptr转换为shared_ptr。

if(temp == nullptr)
    cout << "pa->pb已经释放" << endl;
else
    cout << "pa->pb.lock() << temp->_a" << endl;
posted @ 2025-01-04 01:07  baobaobashi  阅读(3)  评论(0编辑  收藏  举报