现代C++——智能指针

1. std::shared_ptr

std::shared_ptr 是一种智能指针,它能够记录多少个 shared_ptr 共同指向一个对象。

delete,当引用计数变为零的时候就会将对象自动删除。

使用 std::shared_ptr 仍然需要使用 new 来调用,这使得代码出现了某种程度上的不对称。

std::make_shared 就能够用来消除显式的使用 new,所以 std::make_shared 会分配创建传入参数中的对象,并返回这个对象类型的 std::shared_ptr 指针。例如:

cpp 复制代码
#include <iostream>
#include <memory>
void foo(std::shared_ptr<int> i) 
{
    (*i)++;
}
int main() 
{
    auto pointer = std::make_shared<int>(10);
    foo(pointer);
    std::cout << *pointer << std::endl; // 结果是11
    return 0;
    //出了作用范围后自动销毁
}

std::shared_ptr 可以通过 get() 方法来获取原始指针,通过 reset() 来减少一个引用计数,

并通过 use_count() 来查看一个对象的引用计数。例如:

cpp 复制代码
auto pointer = std::make_shared<int>(10);
auto pointer2 = pointer; // 引用计数+1
auto pointer3 = pointer; // 引用计数+1
int *p = pointer.get();  // 这样不会增加引用计数
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl;   // 3
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3

pointer2.reset();
std::cout << "reset pointer2:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl;   // 2
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // pointer2 已 reset; 0
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2
pointer3.reset();
std::cout << "reset pointer3:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl;   // 1
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // pointer3 已 reset; 0

当使用 shared_ptr 的时候每添加一个指向同一个对象的指针就会增加一个计数,这个计数是总计数,所以在打印的第一部分会发现调用三个指针分别查看这个对象的引用数都是 3 。

根据不同的指针使用 reset() 只会消除对应指针对这块内存的所有权,第二处打印前对 p2 进行了减少计数的操作,此时分别打印会发现 p1 和 p3 都是 2 ,而 p2 变成了 0。这是因为每个 shared_ptr 在添加的时候把一个数公共的加进了计数中,所以 reset() 操作把自己对这块内存的所有权放弃后,总计数就会减 1 。但是既然已经放弃了所有权,所以 p2 就无法正常显示刚才那块内存的计数,所以就显示 0。

第三段打印时也是相同的原因。

2. std::unique_ptr

std::unique_ptr 是一种独占的智能指针,它禁止其他智能指针与其共享同一个对象,从而保证代码的安全:

cpp 复制代码
std::unique_ptr<int> pointer = std::make_unique<int>(10); // make_unique 从 C++14 引入
std::unique_ptr<int> pointer2 = pointer; // 非法

既然是独占,那就是不可复制。但是,我们可以利用 std::move 将其转移给其他的 unique_ptr,例如:

cpp 复制代码
#include <iostream>
#include <memory>

struct Foo 
{
    Foo() { std::cout << "Foo::Foo" << std::endl; }
    ~Foo() { std::cout << "Foo::~Foo" << std::endl; }
    void foo() { std::cout << "Foo::foo" << std::endl; }
};

void f(const Foo &) 
{
    std::cout << "f(const Foo&)" << std::endl;
}

int main() 
{
    //创造一个类型为 unique_ptr<Foo> 的智能指针 p1 初始化通过Foo的构造函数
    std::unique_ptr<Foo> p1(std::make_unique<Foo>());
    // p1 不空, 输出
    if (p1) p1->foo();
    {
        //通过 move 将 p1 转移给 p2 完成 p2 的初始化,此时 p1 失去了这块内存的访问权
        std::unique_ptr<Foo> p2(std::move(p1));

        // p2 不空, 输出
        f(*p2);

        // p2 不空, 输出
        if(p2) p2->foo();

        // p1 为空, 无输出
        if(p1) p1->foo();

        //再通过 move 把 p2 的所有权转给 p1
        p1 = std::move(p2);

        // p2 为空, 无输出
        if(p2) p2->foo();

        std::cout << "p2 被销毁" << std::endl;
    }
    // p1 不空, 输出
    if (p1) p1->foo();
    // Foo 的实例会在离开作用域时被销毁
}

3. std::weak_ptr

如果你仔细思考 std::shared_ptr 就会发现依然存在着资源无法释放的问题。看下面这个例子:

cpp 复制代码
class Child;
class Parent 
{
public:
    std::shared_ptr<Child> child; // 爸爸持有儿子的 shared_ptr
    ~Parent() { std::cout << "爸爸走了\n"; }
};

class Child 
{
public:
    std::shared_ptr<Parent> parent; // 儿子持有爸爸的 shared_ptr
    ~Child() { std::cout << "儿子走了\n"; }
};

int main() 
{
    auto dad = std::make_shared<Parent>();
    auto son = std::make_shared<Child>();
    dad->child = son;   // 爸爸指向儿子,计数:儿子对象 = 2 (son + dad->child)
    son->parent = dad;  // 儿子指向爸爸,计数:爸爸对象 = 2 (dad + son->parent)
    // main 结束,dad 和 son 销毁,计数各自减为 1
    // 内存泄漏,两个对象互相抱着,无法释放
}

爸爸要等儿子走才走,儿子要等爸爸走才走,结果谁也走不了,导致内存泄漏。

解决这个问题的办法就是使用弱引用指针 std::weak_ptr,std::weak_ptr 是一种弱引用(相比较而言 std::shared_ptr 就是一种强引用)。

cpp 复制代码
class Child 
{
public:
    std::weak_ptr<Parent> parent; // 改成弱引用!儿子知道有爸爸,但不"拽着"爸爸不让走
    ~Child() { std::cout << "儿子走了\n"; }
};

std::weak_ptr 没有 * 运算符和 -> 运算符,所以不能够对资源进行操作,它可以用于检查std::shared_ptr 是否存在。

使用 expired() 方法能在对象资源未被释放时返回 false,否则返回 true 。除此之外,还可以升级成强引用,通过 lock() 可以升级成临时的 shared_ptr。

相关推荐
爱和冰阔落1 小时前
【Linux】从匿名管道到进程池:任务派发、fd 继承 Bug 与完整实现
android·linux·运维·c++
Ivanqhz1 小时前
php7 闭包实现
开发语言·后端·rust·php
TunerT_TQ1 小时前
Valhalla 静态工程审阅 #021|华为MindSpore 源码证据驱动评测【大厂开源基础设施特辑】
c++·开源·github·mfc·#大模型开源·#华为开源·#深度学习
weixin_440784119 小时前
【HandlerThread实现原理】
android·java·开发语言
天空'之城9 小时前
C 语言工业级通用组件手写 24:互补滤波
c语言·开发语言·互补滤波·嵌入式算法·工业级组件
XR1234567889 小时前
工厂车间无线网络方案对比:AGV漫游哪家强?
开发语言·php
傻啦嘿哟10 小时前
某短视频平台视频爬虫实战:抓取推荐流视频信息,绕过反爬的3种技巧
开发语言·爬虫·python
現実君11 小时前
【硬件进阶】AD22上位替换JLEDA教程
开发语言·php
mubei-12311 小时前
SpringDAO的用法
java·开发语言·数据库