《深入理解C++11》笔记-智能指针unique_ptr、shared_ptr、weak_ptr

本文介绍了C++11中的三种智能指针:unique_ptr、shared_ptr和weak_ptr。详细解释了每种智能指针的特点及用法,并通过实例展示了如何避免内存泄漏和解决循环引用问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇:《深入理解C++11》笔记-强类型枚举
本篇介绍C++11中新增的智能指针:unique_ptr、shared_ptr、weak_ptr。使用智能指针可以免于我们去主动管理内存,智能指针会自动释放内存:

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

int main()
{
    {
        std::unique_ptr<Example> unique_p1(new Example());   // Example()
    }                                                        // ~Example()

    {
        std::shared_ptr<Example> shared_p1(new Example());   // Example()
    }                                                        // ~Example()

    return 0;
}

在智能指针的作用域结束时,会自动释放申请的内存。

unique_ptr

从命名上看,unique_ptr就是独有指针,这里的独有代表什么?我们来看一下例子:

std::unique_ptr<int> p1(new int(0));     // 指向int类型的智能指针
std::unique_ptr<int> p2 = p1;            // 编译失败,unique_ptr不能与其他指针共享同一份内存
std::shared_ptr<int> p3 = p1;            // 编译失败,unique_ptr不能与其他指针共享同一份内存

std::unique_ptr<int> p4 = std::move(p1); // 使用move函数,能够转移内存的所有权
std::shared_ptr<int> p5 = std::move(p4);

从上面的代码可以看到,unique_ptr独占了指向的内存空间,其他智能指针不能与之共享。当释放引用或离开作用域时,内存就会被释放:

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

int main()
{
    std::unique_ptr<Example> p1(new Example());     // Example()
    p1.reset();                                     // ~Example(),释放引用,释放内存

    return 0;
}

shared_ptr

shared_ptr和unique_ptr不同,它运行多个shared_ptr共享一份内存:

std::shared_ptr<int> p1(new int(0));         // 指向int类型的智能指针
std::cout << p1.use_count() << std::endl;    // 1
std::shared_ptr<int> p2 = p1;                // 引用计数增加
std::cout << p1.use_count() << std::endl;    // 2

shared_ptr采用了引用计数的方式来计算有几个shared_ptr拥有该内存,通过use_count成员函数能够拿到引用计数总数。当引用计数为0,或则离开作用域时,内存就会被释放:

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

int main()
{
    std::shared_ptr<Example> p1(new Example());     // Example()
    std::cout << p1.use_count() << std::endl;       // 1
    std::shared_ptr<Example> p2 = p1;
    std::cout << p1.use_count() << std::endl;       // 2
    p2.reset();                                     // 释放引用计数
    std::cout << p1.use_count() << std::endl;       // 1
    p1.reset();                                     // ~Example(),释放引用计数,计数为0,释放内存
    std::cout << p1.use_count() << std::endl;       // 0

    return 0;
}

weak_ptr
weak_ptr不能直接指向内存空间,只能指向shared_ptr:

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

int main()
{
    std::weak_ptr<Example> weak_p1(new Example());               // 不能直接指向内存空间

    std::unique_ptr<Example> unique_p1(new Example()); 
    std::weak_ptr<Example> weak_p2 = unique_p1;                  // 不能指向unique_ptr

    std::shared_ptr<Example> shared_p1(new Example());
    std::weak_ptr<Example> weak_p3 = shared_p1;                    // 不占用引用计数
    std::cout << shared_p1.use_count() << std::endl;               // 1
    std::shared_ptr<Example> shared_p2 = shared_p1;
    std::cout << weak_p3.use_count() << std::endl;                 // 2,同样能获取引用计数

    std::shared_ptr<Example> shared_p3 = weak_p3.lock();           // lock返回shared_ptr对象
    std::cout << shared_p1.use_count() << std::endl;               // 3

    shared_p1.reset();
    shared_p2.reset();
    shared_p3.reset();

    if (!weak_p3.lock())                                          // 智能指针已经释放,weak_ptr.lock返回空指针
    {
        std::cout << "nullptr" << std::endl;
    }

    return 0;
}

weak_ptr指向shared_ptr对象,但是本身不占用引用计数;weak_ptr中的lock成员函数能够对shared_ptr智能指针进行有效性判断,防止空指针引用。

另外,weak_ptr还能解决shared_ptr的循环引用问题,来看一个双向链表的例子:

template<typename T>
struct ListNode{
    T _value;
    std::shared_ptr<ListNode> _prev;
    std::shared_ptr<ListNode> _next;

    ListNode(const T & value)
    :_value(value),_prev(nullptr),_next(nullptr){}

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

void func()
{
    std::shared_ptr<ListNode<int>> a(new ListNode<int>(1));
    std::shared_ptr<ListNode<int>> b(new ListNode<int>(2));

    std::cout<<a.use_count()<<std::endl;   // 1
    std::cout<<b.use_count()<<std::endl;   // 1

    {
        a->_next = b;
        b->_prev = a;
    }

    std::cout<<a.use_count()<<std::endl;   // 2
    std::cout<<b.use_count()<<std::endl;   // 2
}

int main()
{
    func();
    return 0;
}

如上代码,在a和b构成了一个只有两个node的循环链表的时候,a和b互相引用了对方。可以看到,在创建a和b之后,它们的引用计数都是1;相互引用之后,即使出了作用域,引用计数还是2;并且退出函数之后,a和b并没有被析构,因为此时两者的引用计数是1,没有变为0。

这个时候,weak_ptr就有用了,把模板中的shared_ptr换成weak_ptr就不会有这个问题了,因为它不增加引用计数。

template<typename T>
struct ListNode{
    T _value;
    std::weak_ptr<ListNode> _prev;
    std::weak_ptr<ListNode> _next;

    ListNode(const T & value)
    :_value(value){}

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

指针对象使用

现在我们了解了unique_ptr、shared_ptr、weak_ptr,再看看他们指向的对象应该怎么使用:

    std::unique_ptr<int> unique_p1(new int(0)); 
    std::cout << *unique_p1 << std::endl;                // 直接使用指针对象
    std::cout << *unique_p1.get() << std::endl;          // 通过get获取int*

    std::shared_ptr<int> shared_p1(new int(0));
    std::cout << *shared_p1 << std::endl;                // 直接使用指针对象
    std::cout << *shared_p1.get() << std::endl;          // 通过get获取int*

    std::weak_ptr<int> weak_p1 = shared_p1;
    std::cout << *weak_p1 << std::endl;                  // 不能直接使用
    std::cout << *weak_p1.get() << std::endl;            // 不能通过get获取int*
    std::cout << *weak_p1.lock() << std::endl;           // 通过lock获取shared_ptr对象,再使用对象
    std::cout << *weak_p1.lock().get() << std::endl;     // 通过lock获取shared_ptr对象,再使用get获取int*

下一篇:《深入理解C++11》笔记-常量表达式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值