当前位置: 首页 > news >正文

C++ 智能指针

C++ 智能指针(Smart Pointer)是 C++11 引入的用于自动管理动态内存的模板类,其核心作用是通过RAII(资源获取即初始化)机制,在智能指针生命周期结束时自动释放所管理的内存,从而避免传统裸指针(Raw Pointer)可能导致的内存泄漏、重复释放、悬垂指针等问题。

智能指针通过 RAII(Resource Acquisition Is Initialization) idiom 来解决这些问题:

  • 获取资源即初始化:在构造函数中获取资源(分配内存)。
  • 释放资源即析构:在析构函数中自动释放资源。只要智能指针对象超出作用域,无论是因为正常执行还是异常,其析构函数都会被调用,从而保证资源被释放。

所有智能指针都定义在 <memory> 头文件中。

1、std::unique_ptr - 独占所有权指针

std::unique_ptr独占式智能指针,其管理的资源只能被一个 unique_ptr 拥有不允许拷贝(避免多个指针同时管理同一资源),但允许移动(转移所有权)。

1.1 基本用法

#include <memory>
#include <iostream>
using namespace std;class MyClass {
public:MyClass(int id) : id(id) {cout << "MyClass(" << id << ") 构造" << endl;}~MyClass() {cout << "MyClass(" << id << ") 析构" << endl;}void print() {cout << "MyClass id: " << id << endl;}
private:int id;
};int main() {// 1. 创建 unique_ptr(管理动态对象)unique_ptr<MyClass> ptr1(new MyClass(1)); // 直接初始化(不推荐,可能抛异常)auto ptr2 = make_unique<MyClass>(2);     // 推荐:make_unique(更安全,避免内存泄漏)// 2. 访问资源(重载 * 和 ->)(*ptr1).print();  // 等价于 ptr1->print()ptr2->print();// 3. 转移所有权(只能通过 move,原指针变为空)unique_ptr<MyClass> ptr3 = move(ptr1); // ptr1 失去所有权,变为空if (ptr1 == nullptr) {cout << "ptr1 已为空" << endl;}ptr3->print(); // 仍可访问资源// 4. 主动释放资源(reset())ptr3.reset(); // 释放资源,ptr3 变为空if (ptr3 == nullptr) {cout << "ptr3 已释放资源" << endl;}return 0; // ptr2 离开作用域,自动释放资源
}

输出结果

MyClass(1) 构造
MyClass(2) 构造
MyClass id: 1
MyClass id: 2
ptr1 已为空
MyClass id: 1
MyClass(1) 析构
ptr3 已释放资源
MyClass(2) 析构

1.2 注意事项

  • 推荐使用 make_uniquemake_unique<T>(args) 直接在内部构造对象,避免裸 new 可能导致的异常安全问题(如 new 后未传给智能指针前抛异常,导致内存泄漏)。
  • 禁止拷贝unique_ptr 的拷贝构造和拷贝赋值被删除(= delete),只能通过 std::move 转移所有权:
    unique_ptr<MyClass> ptr1 = make_unique<MyClass>(1);
    // unique_ptr<MyClass> ptr2 = ptr1; // 编译错误:禁止拷贝
    unique_ptr<MyClass> ptr2 = move(ptr1); // 正确:转移所有权
    
  • 数组支持unique_ptr 可管理动态数组(自动调用 delete[]):
    auto arr_ptr = make_unique<int[]>(5); // 管理包含5个int的数组
    arr_ptr[0] = 10; // 支持下标访问
    
  • 定制删除器:默认使用 delete 释放资源,可自定义删除器(如释放文件指针、网络连接等):
    // 自定义删除器(释放文件指针)
    auto file_deleter = [](FILE* f) {if (f) {fclose(f);cout << "文件已关闭" << endl;}
    };// 创建带自定义删除器的 unique_ptr
    unique_ptr<FILE, decltype(file_deleter)> file_ptr(fopen("test.txt", "w"), file_deleter);
    

1.3 应用场景

  • 首选用于管理动态生命周期资源。
  • 作为类的成员变量,表示独占资源。
  • 在函数中动态创建对象并返回所有权(编译器会进行返回值优化 RVO,可能连移动都不需要)。
    std::unique_ptr<MyClass> createObject() {return std::make_unique<MyClass>();
    }
    auto obj = createObject();
    

2、std::shared_ptr - 共享所有权指针

多个 shared_ptr 可以共享同一个对象的所有权。通过引用计数机制来跟踪有多少个 shared_ptr 指向同一个对象。当最后一个 shared_ptr 被销毁时,对象才会被删除。

2.1 基本使用

#include <memory>
#include <iostream>
using namespace std;int main() {// 1. 创建 shared_ptr(推荐用 make_shared)shared_ptr<MyClass> ptr1 = make_shared<MyClass>(1);cout << "引用计数: " << ptr1.use_count() << endl; // 1// 2. 拷贝 shared_ptr(引用计数 +1)shared_ptr<MyClass> ptr2 = ptr1; // 拷贝,计数变为 2cout << "ptr1 计数: " << ptr1.use_count() << endl; // 2cout << "ptr2 计数: " << ptr2.use_count() << endl; // 2// 3. 转移部分指针的所有权(计数不变)shared_ptr<MyClass> ptr3 = move(ptr1); // ptr1 变为空,计数仍为 2(ptr2 和 ptr3 共享)cout << "ptr1 是否为空: " << (ptr1 == nullptr ? "是" : "否") << endl; // 是cout << "ptr3 计数: " << ptr3.use_count() << endl; // 2// 4. 释放部分指针(计数 -1)ptr2.reset(); // ptr2 释放,计数变为 1cout << "ptr3 计数: " << ptr3.use_count() << endl; // 1return 0; // ptr3 离开作用域,计数变为 0,资源释放
}

输出结果

MyClass(1) 构造
引用计数: 1
ptr1 计数: 2
ptr2 计数: 2
ptr1 是否为空: 是
ptr3 计数: 2
ptr3 计数: 1
MyClass(1) 析构

2.2 注意事项

  • 推荐使用 make_sharedmake_shared<T>(args) 一次性分配对象和引用计数的内存,效率高于 shared_ptr<T>(new T(args))(后者可能分配两次内存)。
  • 循环引用问题:两个 shared_ptr 互相引用会导致引用计数无法归零,造成内存泄漏(需用 weak_ptr 解决):
    class B; // 前向声明
    class A {
    public:shared_ptr<B> b_ptr; // A 持有 B 的 shared_ptr~A() { cout << "A 析构" << endl; }
    };
    class B {
    public:shared_ptr<A> a_ptr; // B 持有 A 的 shared_ptr~B() { cout << "B 析构" << endl; }
    };int main() {auto a = make_shared<A>();auto b = make_shared<B>();a->b_ptr = b; // 互相引用b->a_ptr = a;// 离开作用域时,a和b的引用计数均为1(互相持有),无法释放,导致内存泄漏return 0;
    }
    
    输出结果(无析构输出,内存泄漏):
    (无任何析构信息,A和B对象未释放)
    
  • 定制删除器:与 unique_ptr 类似,可自定义删除器(所有共享该资源的 shared_ptr 需使用相同删除器):
    // 自定义删除器(释放数组)
    auto arr_deleter = [](int* p) {delete[] p;cout << "数组已释放" << endl;
    };shared_ptr<int> arr_ptr(new int[5], arr_deleter);
    

2.3 应用场景

  • 需要多个指针共享同一个对象时。
  • 需要将指针存入标准容器时(unique_ptr C++11 不能直接存入容器,C++17 可以,但语义仍是独占)。
  • 实现缓存的场景。

3、std::weak_ptr - 弱引用指针

std::weak_ptr 是一种弱引用智能指针,它指向 shared_ptr 管理的资源,但不增加引用计数,主要用于解决 shared_ptr 的循环引用问题

3.1 基本使用

struct B;
struct A {std::shared_ptr<B> b_ptr;~A() { std::cout << "A destroyed\n"; }
};
struct B {std::shared_ptr<A> a_ptr; // 如果这里是shared_ptr,会产生循环引用~B() { std::cout << "B destroyed\n"; }
};// 循环引用导致内存泄漏:
{auto a = std::make_shared<A>();auto b = std::make_shared<B>();a->b_ptr = b; // b的引用计数为2b->a_ptr = a; // a的引用计数为2
} // 离开作用域,a和b的引用计数都减为1,永远不会变为0,对象无法被销毁!// 使用weak_ptr打破循环引用:
struct B;
struct A {std::shared_ptr<B> b_ptr;~A() { std::cout << "A destroyed\n"; }
};
struct B {std::weak_ptr<A> a_weak_ptr; // 使用weak_ptr而不是shared_ptr~B() { std::cout << "B destroyed\n"; }
};{auto a = std::make_shared<A>();auto b = std::make_shared<B>();a->b_ptr = b;b->a_weak_ptr = a; // 这里不会增加a的引用计数!(a的计数仍为1)
} // 离开作用域,a的计数变为0,先被销毁。然后b的计数变为0,也被销毁。
// 输出: A destroyed \n B destroyed

3.2 注意事项

  • 不能直接访问资源weak_ptr 没有重载 *->,必须通过 lock() 获取 shared_ptr 后才能访问资源(避免访问已释放的资源)。
  • 检查资源有效性expired() 方法可判断资源是否已释放(true 表示已释放):
    weak_ptr<MyClass> wp;
    {auto sp = make_shared<MyClass>(1);wp = sp; // wp 指向 sp 管理的资源cout << "资源是否有效: " << (!wp.expired() ? "是" : "否") << endl; // 是
    } // sp 销毁,资源释放
    cout << "资源是否有效: " << (!wp.expired() ? "是" : "否") << endl; // 否
    

3.3 适用场景

  • 打破 shared_ptr 的循环引用
  • 实现缓存观察者模式等,观察者不需要拥有被观察对象的所有权。

4、常见问题

  1. std::unique_ptrstd::shared_ptr 的根本区别是什么?
    根本区别在于所有权语义
    unique_ptr 表示独占所有权,一个对象只能由一个 unique_ptr 拥有,它不能被拷贝,只能移动。
    shared_ptr 表示共享所有权,多个 shared_ptr 可以共享同一个对象的所有权,通过引用计数机制管理生命周期。

为什么更推荐使用 make_shared 而不是直接 new 来创建 shared_ptr
主要有两个原因:

  1. 性能make_shared 通常只进行一次内存分配,同时为对象和控制块分配内存,而 new 需要两次分配。
  2. 异常安全make_shared 避免了在函数参数求值过程中可能发生异常而导致的内存泄漏问题。
  1. 什么是循环引用?weak_ptr 是如何解决它的?
    循环引用是指两个或多个对象通过 shared_ptr 互相持有,导致它们的引用计数永远无法降为 0,从而无法被析构,产生内存泄漏。weak_ptr 通过提供一种不增加引用计数的“弱引用”来打破这种循环。它将循环中的某一个 shared_ptr 替换为 weak_ptr,这样就不会阻止所指向对象的销毁。

  2. 能否从一个 weak_ptr 直接访问资源?如果不能,应该如何做?
    不能。因为 weak_ptr 不拥有资源,它不知道资源是否已被释放。必须使用 lock() 方法,它会返回一个 shared_ptr。如果资源还存在,这个 shared_ptr 是有效的(并且会增加引用计数);如果资源已被释放,则返回一个空的 shared_ptr必须检查 lock() 的返回值

  3. 智能指针能否用于管理数组?
    可以。

  • std::unique_ptr 完全支持数组:std::unique_ptr<T[]>,并且重载了 operator[]。使用 make_unique<T[]>(size) (C++17)。
  • std::shared_ptr 对数组的支持不如 unique_ptr 直接。在 C++17 及以后,可以用 std::make_shared<T[]>(size),但 API 不如 unique_ptr 完善。在 C++17 之前,通常需要为 shared_ptr 指定一个删除器,如 std::shared_ptr<int> sp(new int[10], std::default_delete<int[]>());
  1. 智能指针的大小是多少?开销有多大?
  • std::unique_ptr:大小通常等于一个指针(例如 8 字节 on x64),开销极小,几乎就是封装了一个原生指针和一些编译期决定的逻辑。
  • std::shared_ptr:大小通常是两个指针(例如 16 字节 on x64)。一个指向管理的对象,另一个指向包含引用计数等的控制块。其开销包括额外的内存分配(控制块)和维护引用计数的原子操作。
  • std::weak_ptr:大小通常和 shared_ptr 一样(两个指针),它也需要访问控制块。
http://www.hskmm.com/?act=detail&tid=7312

相关文章:

  • 数据类型
  • iphone运行windows系统
  • NVR接入录像回放平台EasyCVR视频融合平台语音对讲配置指南
  • Ubuntu filebrowser网盘工具安装
  • 图片结构 - voasem
  • ESP32做AP,ESP8266做station,遥控
  • 实用指南:25年高联:一试填空题解析(下篇)
  • Spring AOP 面向切面编程 - 浪矢
  • jvm内存泄漏的排查tips总结
  • IPA
  • Chromium历史版本下载方式
  • 【ACM出版】第三届物联网与云计算技术国际学术会议 (IoTCCT 2025)
  • 2025年最全 Wiki 管理工具测评:ONES、Confluence、Notion......哪个更适合你?
  • 详细介绍:用户争夺与智能管理:定制开发开源AI智能名片S2B2C商城小程序的战略价值与实践路径
  • PlorarD(WEB中等)
  • 神经网络稀疏化设计构架方式和原理深度解析
  • 天下拍拍卖系统:二方系统也能扩展三方平台功能
  • express使用redis
  • day07 课程
  • 111
  • 排序实现java - 教程
  • .net core 发布到 iis 步骤
  • kylin SP2安装mysql8.4.5
  • 微信社群机器人接口
  • C++的枚举类
  • Revit二次开发 钢筋生成API(一)
  • 方法
  • 详细介绍:PHP基础-语法初步(第七天)
  • 如何通过Python SDK 删除 Collection
  • maven项目连接DM数据库和基本sql使用