0

0

C++智能指针有哪些基本类型 解析unique_ptr shared_ptr weak_ptr核心区别

P粉602998670

P粉602998670

发布时间:2025-08-01 08:03:01

|

937人浏览过

|

来源于php中文网

原创

c++++智能指针通过raii机制实现自动内存管理,有效避免内存泄漏和悬空指针。1. unique_ptr以独占所有权确保资源安全,不可复制只能移动,适用于单一所有权场景;2. shared_ptr采用引用计数实现共享所有权,适用于多模块共享资源但需警惕循环引用;3. weak_ptr作为观察者不增加引用计数,用于打破循环引用并安全观察对象生命周期。这三种智能指针各具特性,满足不同内存管理需求,是现代c++编程的核心工具

C++智能指针有哪些基本类型 解析unique_ptr shared_ptr weak_ptr核心区别

C++智能指针的核心在于它们通过RAII(资源获取即初始化)原则,为动态分配的内存提供自动化的生命周期管理,从而有效避免了内存泄漏和悬空指针等常见问题。它们本质上是封装了裸指针的类模板,并在对象析构时自动释放所管理的资源。其中,unique_ptrshared_ptrweak_ptr标准库中最常用的三种基本类型,各自以不同的所有权语义满足了多样化的内存管理需求。

C++智能指针有哪些基本类型 解析unique_ptr shared_ptr weak_ptr核心区别

智能指针的出现,无疑是C++现代编程实践中一个里程碑式的进步。回想过去,我们手动管理内存,newdelete 的配对就像一场永无止境的华尔兹,稍不留神,舞步就乱了,内存泄漏、重复释放,各种运行时错误接踵而至。那感觉,就像你手里拿着一把钥匙,却不知道哪把锁是它的归宿,或者更糟,你把钥匙扔了,但门还没关。智能指针的出现,就是把这把钥匙和锁绑定在一起,当锁不再需要时,钥匙自动消失。这背后,是RAII(Resource Acquisition Is Initialization)这个强大理念在支撑。简单来说,就是把资源的生命周期绑定到一个对象的生命周期上,当对象被创建时获取资源,当对象被销毁时释放资源。

unique_ptr:独占所有权的智能指针如何确保资源安全?

在我看来,unique_ptr 是智能指针家族中最直接、最干净利落的存在。它的名字已经说明了一切:独占所有权。这意味着一个 unique_ptr 实例独一无二地拥有它所指向的资源,任何时候都只有一个 unique_ptr 管理着那块内存。这种独占性带来了极大的安全性:你不用担心资源被多个地方误删,也不用担心它在某个角落被遗忘。

立即学习C++免费学习笔记(深入)”;

C++智能指针有哪些基本类型 解析unique_ptr shared_ptr weak_ptr核心区别

unique_ptr 的一个显著特点是它不能被复制,只能被移动。这就像你拥有一件独一无二的艺术品,你可以把它从一个房间搬到另一个房间(移动),但你不能同时拥有两件一模一样的(复制)。这种移动语义确保了所有权的清晰转移,一旦所有权从一个 unique_ptr 转移到另一个,原来的 unique_ptr 就不再指向任何资源了。这在函数返回动态分配的对象时特别有用,或者在将所有权从一个容器转移到另一个容器时。

#include 
#include 

class MyResource {
public:
    MyResource(int id) : id_(id) {
        std::cout << "MyResource " << id_ << " created." << std::endl;
    }
    ~MyResource() {
        std::cout << "MyResource " << id_ << " destroyed." << std::endl;
    }
    void doSomething() {
        std::cout << "MyResource " << id_ << " doing something." << std::endl;
    }
private:
    int id_;
};

// 假设一个函数返回一个unique_ptr
std::unique_ptr createResource(int id) {
    return std::make_unique(id); // 返回一个unique_ptr
}

int main() {
    std::unique_ptr ptr1 = std::make_unique(1);
    ptr1->doSomething();

    // 移动所有权
    std::unique_ptr ptr2 = std::move(ptr1); 
    if (!ptr1) { // ptr1现在是空的
        std::cout << "ptr1 is now null." << std::endl;
    }
    ptr2->doSomething();

    // 函数返回的unique_ptr
    std::unique_ptr ptr3 = createResource(3);
    ptr3->doSomething();

    // unique_ptr也可以管理数组
    std::unique_ptr arrPtr = std::make_unique(5);
    arrPtr[0] = 10;
    std::cout << "arrPtr[0]: " << arrPtr[0] << std::endl;

    // 当unique_ptr离开作用域时,资源自动释放
    return 0; 
}

这段代码清晰地展示了 unique_ptr 的独占性和移动语义。当 ptr2 = std::move(ptr1) 执行后,ptr1 就失去了对资源的控制,变成了空指针。而 ptr2 接管了所有权。当 ptr2ptr3arrPtr 离开 main 函数的作用域时,它们所管理的资源会自动被销毁,无需手动调用 delete。这种机制极大地简化了资源管理,降低了出错的概率。

C++智能指针有哪些基本类型 解析unique_ptr shared_ptr weak_ptr核心区别

shared_ptr:共享所有权模型在复杂对象管理中的应用与挑战

如果说 unique_ptr 是独行侠,那 shared_ptr 就是一个团队合作者。它允许多个 shared_ptr 实例共同拥有和管理同一块资源。这在很多场景下非常有用,比如一个数据对象需要被多个模块同时访问,并且这些模块的生命周期可能不同步。shared_ptr 通过内部的引用计数机制来实现这一点:每当一个新的 shared_ptr 共享同一资源时,引用计数就增加;当一个 shared_ptr 离开作用域或被重置时,引用计数就减少。当引用计数降到零时,表示没有 shared_ptr 再指向该资源了,资源便会自动释放。

它的应用场景很广,比如在图形渲染中,一个纹理可能被多个模型共享;或者在一个缓存系统中,同一个数据项可能被多个请求引用。

Kaiber
Kaiber

Kaiber是一个视频生成引擎,用户可以根据自己的图片或文字描述创建视频

下载
#include 
#include 

class DataObject {
public:
    DataObject(const std::string& name) : name_(name) {
        std::cout << "DataObject " << name_ << " created." << std::endl;
    }
    ~DataObject() {
        std::cout << "DataObject " << name_ << " destroyed." << std::endl;
    }
    void showName() {
        std::cout << "My name is " << name_ << std::endl;
    }
private:
    std::string name_;
};

int main() {
    std::shared_ptr s_ptr1 = std::make_shared("SharedData");
    std::cout << "s_ptr1 use_count: " << s_ptr1.use_count() << std::endl;

    {
        std::shared_ptr s_ptr2 = s_ptr1; // 复制,增加引用计数
        std::cout << "s_ptr1 use_count: " << s_ptr1.use_count() << std::endl;
        s_ptr2->showName();
    } // s_ptr2 离开作用域,引用计数减少

    std::cout << "s_ptr1 use_count after s_ptr2 scope: " << s_ptr1.use_count() << std::endl;

    // 当所有shared_ptr都失效时,DataObject才会被销毁
    return 0;
}

这段代码展示了 shared_ptr 的引用计数如何工作。s_ptr2 的创建和销毁都影响了 s_ptr1 所指向对象的引用计数。只有当 s_ptr1 也离开作用域时,DataObject 才会被销毁。

然而,shared_ptr 并非没有缺点。最大的挑战,也是它在使用时最需要警惕的问题,就是循环引用。如果两个或多个 shared_ptr 相互持有对方的 shared_ptr,就会形成一个闭环,导致它们的引用计数永远不会降到零,即使它们在逻辑上已经不再需要了,所管理的资源也永远不会被释放,这实际上就是一种内存泄漏。这种问题在设计复杂的图结构或双向关联时尤其容易发生。解决这个问题,就需要引入 weak_ptr

weak_ptr:如何巧妙解决shared_ptr的循环引用问题并观察对象生命周期?

weak_ptrshared_ptr 的一个“伴侣”,它的设计初衷就是为了解决 shared_ptr 的循环引用问题,同时它也提供了一种非侵入式地观察对象生命周期的方式。weak_ptr 不拥有它所指向的资源,它只是一个“观察者”,因此它不会增加资源的引用计数。这就像你给一个朋友发了一张名片,上面写着他的住址,但你并没有拥有他的房子。

当你想通过 weak_ptr 访问资源时,你需要先调用它的 lock() 方法。lock() 会尝试返回一个 shared_ptr。如果资源仍然存在(即有至少一个 shared_ptr 还在管理它),lock() 就会成功返回一个有效的 shared_ptr;如果资源已经被销毁(所有 shared_ptr 都已失效),lock() 就会返回一个空的 shared_ptr。这种机制使得 weak_ptr 成为检查资源是否仍然存活的理想工具。

我们来看一个典型的循环引用场景以及 weak_ptr 如何打破它:

#include 
#include 
#include 

class B; // 前向声明

class A {
public:
    std::shared_ptr b_ptr; // 强引用
    std::string name;

    A(const std::string& n) : name(n) {
        std::cout << "A " << name << " created." << std::endl;
    }
    ~A() {
        std::cout << "A " << name << " destroyed." << std::endl;
    }
};

class B {
public:
    // 方案一:使用shared_ptr,会造成循环引用
    // std::shared_ptr a_ptr; 

    // 方案二:使用weak_ptr,打破循环引用
    std::weak_ptr a_ptr; // 弱引用

    std::string name;

    B(const std::string& n) : name(n) {
        std::cout << "B " << name << " created." << std.endl;
    }
    ~B() {
        std::cout << "B " << name << " destroyed." << std::endl;
    }
    void showAPtr() {
        if (auto sp = a_ptr.lock()) { // 尝试获取shared_ptr
            std::cout << "B " << name << " sees A " << sp->name << std::endl;
        } else {
            std::cout << "B " << name << " cannot see A (A has been destroyed)." << std::endl;
        }
    }
};

int main() {
    std::cout << "--- 场景一:模拟循环引用 (如果B使用shared_ptr) ---" << std::endl;
    // 如果B::a_ptr是shared_ptr,这里A和B都不会被销毁
    // {
    //     std::shared_ptr a = std::make_shared("ObjA");
    //     std::shared_ptr b = std::make_shared("ObjB");
    //     a->b_ptr = b;
    //     b->a_ptr = a; // 循环引用形成
    // } // A和B的引用计数永远不会降到0,导致内存泄漏
    // std::cout << "A and B should have been destroyed, but might not be due to circular reference." << std::endl;

    std::cout << "\n--- 场景二:使用weak_ptr解决循环引用 ---" << std::endl;
    {
        std::shared_ptr a = std::make_shared("ObjA_Weak");
        std::shared_ptr b = std::make_shared("ObjB_Weak");

        a->b_ptr = b;       // A强引用B
        b->a_ptr = a;       // B弱引用A

        std::cout << "A use_count: " << a.use_count() << std::endl; // 1 (来自a) + 1 (来自b_ptr) = 2
        std::cout << "B use_count: " << b.use_count() << std::endl; // 1 (来自b) + 1 (来自a_ptr) = 2 (这里是错误的,a->b_ptr是强引用,a->b_ptr会增加b的引用计数,但是b->a_ptr是弱引用,不会增加a的引用计数)
        // 修正:A use_count: 1 (来自a)
        // B use_count: 1 (来自b) + 1 (来自a->b_ptr) = 2

        std::cout << "A use_count (after assignment): " << a.use_count() << std::endl;
        std::cout << "B use_count (after assignment): " << b.use_count() << std::endl;


        b->showAPtr(); // B可以访问A
    } // a 和 b 离开作用域,引用计数降为0,A和B都被销毁
    std::cout << "A and B should be destroyed now." << std::endl;

    std::cout << "\n--- 场景三:weak_ptr作为观察者 ---" << std::endl;
    std::shared_ptr data = std::make_shared("ObservedData");
    std::weak_ptr observer = data;

    if (auto sp = observer.lock()) {
        std::cout << "Observer can still see: ";
        sp->showName();
    } else {
        std::cout << "Observer cannot see data." << std::endl;
    }

    data.reset(); // 销毁data指向的对象

    if (auto sp = observer.lock()) {
        std::cout << "Observer can still see: ";
        sp->showName();
    } else {
        std::cout << "Observer cannot see data (it's gone)." << std::endl;
    }

    return 0;
}

场景二 中,当 ab 离开作用域时,a 的引用计数降为0(因为它只被 main 函数中的 ab->a_ptr 弱引用,而弱引用不计入),b 的引用计数也降为0(因为它只被 main 函数中的 ba->b_ptr 强引用)。这样,AB 对象都能被正确销毁,避免了内存泄漏。

场景三 则展示了 weak_ptr 作为纯粹观察者的能力。它不延长对象的生命周期,只是在对象存在时提供访问途径,在对象被销毁后安全地返回空指针,这在缓存管理、事件监听器等场景中非常有用。

总的来说,unique_ptr 适用于单一所有权、清晰生命周期的资源;shared_ptr 适用于多所有权、复杂生命周期的资源,但需要警惕循环引用;而 weak_ptr 则是 shared_ptr 的补充,用于打破循环引用和安全地观察对象生命周期。理解它们的区别和适用场景,是写出健壮、高效C++代码的关键。

相关专题

更多
resource是什么文件
resource是什么文件

Resource文件是一种特殊类型的文件,它通常用于存储应用程序或操作系统中的各种资源信息。它们在应用程序开发中起着关键作用,并在跨平台开发和国际化方面提供支持。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

143

2023.12.20

空指针异常处理
空指针异常处理

本专题整合了空指针异常解决方法,阅读专题下面的文章了解更多详细内容。

20

2025.11.16

数据库Delete用法
数据库Delete用法

数据库Delete用法:1、删除单条记录;2、删除多条记录;3、删除所有记录;4、删除特定条件的记录。更多关于数据库Delete的内容,大家可以访问下面的文章。

266

2023.11.13

drop和delete的区别
drop和delete的区别

drop和delete的区别:1、功能与用途;2、操作对象;3、可逆性;4、空间释放;5、执行速度与效率;6、与其他命令的交互;7、影响的持久性;8、语法和执行;9、触发器与约束;10、事务处理。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

208

2023.12.29

PHP 命令行脚本与自动化任务开发
PHP 命令行脚本与自动化任务开发

本专题系统讲解 PHP 在命令行环境(CLI)下的开发与应用,内容涵盖 PHP CLI 基础、参数解析、文件与目录操作、日志输出、异常处理,以及与 Linux 定时任务(Cron)的结合使用。通过实战示例,帮助开发者掌握使用 PHP 构建 自动化脚本、批处理工具与后台任务程序 的能力。

22

2025.12.13

php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

150

2025.12.31

php网站源码教程大全
php网站源码教程大全

本专题整合了php网站源码相关教程,阅读专题下面的文章了解更多详细内容。

88

2025.12.31

视频文件格式
视频文件格式

本专题整合了视频文件格式相关内容,阅读专题下面的文章了解更多详细内容。

90

2025.12.31

不受国内限制的浏览器大全
不受国内限制的浏览器大全

想找真正自由、无限制的上网体验?本合集精选2025年最开放、隐私强、访问无阻的浏览器App,涵盖Tor、Brave、Via、X浏览器、Mullvad等高自由度工具。支持自定义搜索引擎、广告拦截、隐身模式及全球网站无障碍访问,部分更具备防追踪、去谷歌化、双内核切换等高级功能。无论日常浏览、隐私保护还是突破地域限制,总有一款适合你!

61

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 3.2万人学习

Pandas 教程
Pandas 教程

共15课时 | 0.9万人学习

ASP 教程
ASP 教程

共34课时 | 3.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号