现代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。

相关推荐
余额瞒着我当琳5 分钟前
C++多态深入解析:多态概念,多态实现条件,虚函数表与内存分布,常见问题及注意事项
c++·算法
ocean210325 分钟前
2025-2026年C++专题大厂面试高频问题
开发语言·c++·面试
白远山32 分钟前
健身场馆无人自动化系统实战指南:从架构设计到部署全流程解析
java·开发语言·架构·需求分析
程序猿编码43 分钟前
扔掉 Python!纯 C++ 本地跑扩散 TTS,GGML 加持,支持 RK3588 NPU 加速
c++·计算机视觉·大模型·transformer·rk3588·推理·ggml
郝学胜-神的一滴1 小时前
C++20模板元编程 02:吃透模板基础核心四大核心知识点
开发语言·数据结构·c++·程序人生·软件工程·visual studio
troy1281 小时前
Visual Studio Code 详细教程:从入门到高效开发
开发语言·python
泡海椒1 小时前
jquick-pdf 超详细入门教程:Java 轻量级 HTML 模板生成 PDF 工具
java·开发语言
八年。。1 小时前
WSL学习(三)——搭建Linux + C++ + SOME/IP 基础开发环境
开发语言·笔记·ubuntu
代码王同学1 小时前
2026.9.15日C++类和对象学习收获笔记
c语言·c++
孙启超1 小时前
【AI开发之Rust】第 6 课:结构体、枚举与方法 —— 开始定义自己的类型
开发语言·人工智能·后端·ai·rust