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

cpp自学 day26(智能指针)

C++ 智能指针学习笔记

智能指针是 C++11 引入的重要特性,旨在自动管理动态分配的内存,有效解决传统裸指针(raw pointer)可能导致的内存泄漏、野指针、重复释放等问题。它们的核心思想是RAII (Resource Acquisition Is Initialization),即资源在对象构造时获取,在对象析构时自动释放。


🎯 智能指针的类型

C++ 主要提供了三种智能指针:unique_ptrshared_ptrweak_ptr

1. unique_ptr (独占所有权)

  • 概念: 独占性指针。unique_ptr 管理的内存只能由一个智能指针拥有。当 unique_ptr 对象被销毁时,它所指向的内存会被自动释放。
  • 特性:
    • 独占性: 任何时候,只有一个 unique_ptr 可以指向给定的内存地址。
    • 无法复制: unique_ptr 不能被复制(= 操作符),但可以被移动move())。这意味着所有权可以转移。
    • 轻量: 大小与裸指针相同(通常),性能开销极小。
  • 何时使用:
    • 当你确定某块内存资源只会被一个所有者管理时。
    • 作为函数返回值(通过移动语义安全返回动态分配的对象)。
    • 用作类成员,表示该类独占某个资源。

示例:

#include <iostream>
#include <memory> // 包含智能指针的头文件
#include <utility> // 包含 moveusing namespace std; // 使用命名空间,以便直接使用 cout, endl, unique_ptr 等class MyClass {
public:MyClass() { cout << "MyClass 构造" << endl; }~MyClass() { cout << "MyClass 析构" << endl; }void greet() { cout << "Hello from MyClass!" << endl; }
};void processUniquePtr(unique_ptr<MyClass> ptr) {if (ptr) {ptr->greet();}// ptr 在这里离开作用域,MyClass 对象被自动销毁
}int main() {// 1. 创建 unique_ptr// (1)推荐使用 make_unique 来创建,更安全、更高效unique_ptr<MyClass> p1 = make_unique<MyClass>(); if (p1) {p1->greet();}//(2)第二种创建 unique_ptr的方式//unique_ptr<MyClass> p1(new MyClass());// 2. 所有权转移 (移动)// unique_ptr<MyClass> p2 = p1; // 编译错误!不能复制unique_ptr<MyClass> p2 = move(p1); // p1 现在为空,p2 拥有所有权if (p1) {cout << "p1 仍然有效" << endl;} else {cout << "p1 已经为空" << endl; // 输出此行}if (p2) {p2->greet(); }// 3. 作为函数参数传递 (所有权转移)cout << "\n--- 调用 processUniquePtr ---" << endl;unique_ptr<MyClass> p3 = make_unique<MyClass>();processUniquePtr(move(p3)); // p3 的所有权转移到函数参数 ptr// p3 在这里也变为空cout << "\n--- main 函数结束 ---" << endl; // p2 离开作用域,MyClass 析构return 0;
}

make_unique与unique_ptr(new T())创建unique_ptr指针的区别

  • 安全性(防范内存泄漏):

    • make_unique 更安全。 即使在某些复杂表达式中发生异常,它也能确保内存不会泄漏。它把内存分配和智能指针绑定做成了一个“原子操作”。
    • unique_ptr(new T()) 在极少数情况下(比如在函数参数求值时),如果 new T() 后、unique_ptr 绑定前发生异常,可能导致内存泄漏。
  • 效率(内存分配次数):

    • make_unique 通常更高效。 它通常只需要一次内存分配,同时为对象和管理指针的数据分配空间。
    • unique_ptr(new T()) 涉及两次内存分配:一次给 new T() 的对象,一次给 unique_ptr 自身。

2. shared_ptr (共享所有权)

  • 概念: 共享性指针。shared_ptr 允许多个智能指针共同拥有同一块内存资源。它通过引用计数 (reference count) 来管理内存,只有当所有指向该内存的 shared_ptr 都被销毁时,内存才会被释放。
  • 特性:
    • 共享性: 可以被复制,多个 shared_ptr 可以指向同一个对象。
    • 引用计数: 内部维护一个引用计数,记录有多少个 shared_ptr 指向同一个对象。
    • 自动释放: 当引用计数归零时,内存自动释放。
    • 循环引用问题: 存在循环引用(Cyclic Reference)的风险,可能导致内存泄漏(两个或多个 shared_ptr 相互引用,导致引用计数永远不为零)。
  • 何时使用:
    • 当一个内存资源可能被多个模块或对象共享时。
    • 工厂方法返回对象时。
    • 实现观察者模式或事件系统。

示例:

#include <iostream>
#include <memory> // 包含智能指针的头文件using namespace std; // 使用命名空间,以便直接使用 cout, endl, shared_ptr 等class MyClass {
public:MyClass() { cout << "MyClass 构造" << endl; }~MyClass() { cout << "MyClass 析构" << endl; }void greet() { cout << "Hello from MyClass!" << endl; }
};void showSharedPtr(shared_ptr<MyClass> ptr) { // 参数是 shared_ptr 副本cout << "  进入 showSharedPtr 函数,引用计数: " << ptr.use_count() << endl;if (ptr) {ptr->greet();}cout << "  离开 showSharedPtr 函数" << endl;// ptr 离开作用域,引用计数 -1
}int main() {// 1. 创建 shared_ptr// 推荐使用 make_shared 来创建,更安全、更高效shared_ptr<MyClass> sp1 = make_shared<MyClass>(); cout << "sp1 创建后,引用计数: " << sp1.use_count() << endl; // 输出: 1// 2. 复制 shared_ptr (共享所有权)shared_ptr<MyClass> sp2 = sp1; // 复制,引用计数增加cout << "sp2 复制后,引用计数: " << sp1.use_count() << endl; // 输出: 2// 3. 作为函数参数传递 (引用计数增加)cout << "\n--- 调用 showSharedPtr ---" << endl;showSharedPtr(sp1); // sp1 复制给函数参数 ptrcout << "--- showSharedPtr 返回后,sp1 引用计数: " << sp1.use_count() << endl; // 输出: 2 (因为函数参数的副本已销毁)// 4. 一个 shared_ptr 离开作用域{shared_ptr<MyClass> sp3 = sp1; // 又一个副本,引用计数增加到 3cout << "sp3 在作用域内,引用计数: " << sp1.use_count() << endl; // 输出: 3} // sp3 离开作用域,引用计数减少到 2cout << "sp3 离开作用域后,引用计数: " << sp1.use_count() << endl; // 输出: 2sp1.reset(); // 显式释放 sp1 持有的对象,引用计数 -1 (变为 1)cout << "sp1 reset 后,引用计数: " << sp2.use_count() << endl; // 输出: 1cout << "\n--- main 函数结束 ---" << endl; // sp2 离开作用域,引用计数 -1 (变为 0),MyClass 对象析构return 0;
}

3. weak_ptr (非拥有性引用)

  • 概念: weak_ptr 是一种非拥有性的智能指针。它不增加所指向对象的引用计数,因此不会阻止对象被释放。
  • 特性:
    • 不拥有: 不拥有对象的管理权,因此不会增加引用计数。
    • 解决循环引用: 主要用于解决 shared_ptr 的循环引用问题。
    • 安全性: weak_ptr 可以检查它所指向的对象是否仍然存在。如果对象已被销毁,weak_ptr 会变为空。
    • 使用前必须转换为 shared_ptr 无法直接通过 weak_ptr 访问对象,必须先通过 lock() 方法获取一个 shared_ptr
  • 何时使用:
    • 打破 shared_ptr 之间的循环引用。
    • 观察者模式中,观察者持有被观察对象的 weak_ptr,避免被观察者无法被释放。
    • 缓存机制中,缓存项可以持有资源的 weak_ptr,允许资源在内存不足时被清除。

示例(解决循环引用):

#include <iostream>
#include <memory>using namespace std; // 使用命名空间class B; // 前向声明class A {
public:shared_ptr<B> b_ptr; // A 拥有 BA() { cout << "A 构造" << endl; }~A() { cout << "A 析构" << endl; }void set_b(shared_ptr<B> b) { b_ptr = b; }
};class B {
public:weak_ptr<A> a_ptr; // B 弱引用 A,不增加 A 的引用计数B() { cout << "B 构造" << endl; }~B() { cout << "B 析构" << endl; }void set_a(shared_ptr<A> a) { a_ptr = a; }void access_a() {if (shared_ptr<A> temp_a = a_ptr.lock()) { // 尝试获取 shared_ptrcout << "  B 成功访问 A (引用计数: " << temp_a.use_count() << ")" << endl;} else {cout << "  B 无法访问 A,A 已被销毁" << endl;}}
};int main() {cout << "--- 正常情况下(无循环引用)---" << endl;{shared_ptr<A> pa_normal = make_shared<A>();shared_ptr<B> pb_normal = make_shared<B>();// pa_normal 持有 pb_normal,但 pb_normal 不持有 pa_normalpa_normal->set_b(pb_normal); cout << "pa_normal 引用计数: " << pa_normal.use_count() << endl; // 1cout << "pb_normal 引用计数: " << pb_normal.use_count() << endl; // 2 (pa_normal 和 pb_normal 都持有)} // pa_normal, pb_normal 离开作用域,引用计数归零,对象正常析构cout << "\n--- 解决循环引用(使用 weak_ptr)---" << endl;// 这是演示 weak_ptr 解决 shared_ptr 循环引用的关键代码块{shared_ptr<A> pa = make_shared<A>(); // A 构造shared_ptr<B> pb = make_shared<B>(); // B 构造pa->set_b(pb); // A 强引用 B (b_ptr 是 shared_ptr)pb->set_a(pa); // B 弱引用 A (a_ptr 是 weak_ptr,不增加 A 的引用计数)cout << "pa 引用计数: " << pa.use_count() << endl; // 1 (只有 pa 自己)cout << "pb 引用计数: " << pb.use_count() << endl; // 2 (pa->b_ptr 和 pb 自己)pb->access_a(); // B 能够通过 weak_ptr 临时获取 A 的 shared_ptr 并访问} // pa, pb 离开作用域,引用计数归零,A 和 B 都正常析构cout << "\n--- main 函数结束 ---" << endl;return 0;
}

unique_ptr 常用方法清单

  • unique_ptr():创建空指针。
  • unique_ptr(ptr):接管裸指针 ptr 的所有权。
  • unique_ptr(unique_ptr&& other):移动构造,从 other 转移所有权。
  • operator=(unique_ptr&& other):移动赋值,转移所有权。
  • operator=(nullptr_t):将指针置空。
  • get():返回存储的裸指针。
  • release():释放所有权,返回裸指针并置空自身(返回的裸指针需手动管理)。
  • reset(ptr = nullptr):释放当前对象,接管新裸指针 ptr 的所有权(或置空)。
  • swap(other):与另一个 unique_ptr 交换对象。
  • operator bool():判断指针是否为空。
  • operator*():解引用,获取所指对象。
  • operator->():箭头运算符,访问所指对象成员。

shared_ptr 常用方法清单

  • shared_ptr():创建空指针。
  • shared_ptr(ptr):接管裸指针 ptr 的所有权。
  • shared_ptr(const shared_ptr& other):复制构造,增加引用计数。
  • shared_ptr(shared_ptr&& other):移动构造,转移所有权(不改变引用计数)。
  • shared_ptr(weak_ptr wp):从 weak_ptr 构造 shared_ptr(如果对象存在)。
  • operator=(const shared_ptr& other):复制赋值,增加引用计数。
  • operator=(shared_ptr&& other):移动赋值。
  • operator=(nullptr_t):将指针置空。
  • use_count():返回当前指向同一对象的 shared_ptr 数量。
  • get():返回存储的裸指针。
  • reset(ptr = nullptr):释放当前对象,接管新裸指针 ptr 的所有权(或置空)。
  • swap(other):与另一个 shared_ptr 交换对象。
  • operator bool():判断指针是否为空。
  • operator*():解引用,获取所指对象。
  • operator->():箭头运算符,访问所指对象成员。
  • owner_before(other):比较拥有者次序(用于排序)。

weak_ptr 常用方法清单

  • weak_ptr():创建空指针。
  • weak_ptr(const shared_ptr& sp):从 shared_ptr 构造 weak_ptr
  • weak_ptr(const weak_ptr& wp):复制构造。
  • weak_ptr(weak_ptr&& wp):移动构造。
  • operator=(const shared_ptr& sp):从 shared_ptr 赋值。
  • operator=(const weak_ptr& wp):复制赋值。
  • operator=(weak_ptr&& wp):移动赋值。
  • lock():尝试获取一个 shared_ptr。如果对象存在则返回非空 shared_ptr,否则返回空。
  • expired():检查所指对象是否已被销毁。
  • use_count():返回所观察对象的 shared_ptr 引用计数。
  • reset():将 weak_ptr 置空。
  • swap(other):与另一个 weak_ptr 交换对象。
  • owner_before(other):比较拥有者次序。

💡 智能指针的优势

  • 自动内存管理: 无需手动 delete,避免内存泄漏。
  • 避免野指针: 当智能指针离开作用域时,资源会被释放,指向该资源的智能指针也会失效。
  • 异常安全: 即使在函数发生异常时,智能指针也能保证内存的正确释放。
  • 代码简洁: 减少了手动管理内存的复杂性,使代码更清晰。

⚠️ 使用注意事项

  • 总是使用 make_uniquemake_shared 这是创建智能指针的最佳实践,比直接使用 new 更安全(防止异常和资源泄漏)和更高效。
  • 避免裸指针和智能指针混用: 尽量不要将智能指针管理的对象的裸指针暴露出去,或在两者之间频繁转换,这容易造成混乱和错误。如果需要获取裸指针,使用 get() 方法,但要谨慎使用。
  • unique_ptr 不可复制,只能移动: 如果你需要转移所有权,请使用 move()
  • shared_ptr 的循环引用问题: 这是最常见的陷阱。当两个对象互相持有对方的 shared_ptr 时,它们的引用计数永远不会归零,导致内存泄漏。使用 weak_ptr 来打破循环引用
  • 不要用同一个裸指针初始化多个智能指针: 这会导致重复释放和未定义行为。
http://www.xdnf.cn/news/941743.html

相关文章:

  • 基于算法竞赛的c++编程(23)原码,反码,补码
  • 《双指针》题集
  • [特殊字符]01Linux基础入门教程——从起源到核心概念
  • 高等数学 | 第八章-向量值函数的积分与场论
  • JavaScript 语法结构
  • MySQL 索引失效:六大场景与原理剖析
  • 官网Numpy教程
  • leetcode.多数元素
  • 【PhysUnits】17.1 补充数值后量纲系统实现解析 (dimension.rs)
  • 一键压缩图片工具
  • 2000-2020年各省第三产业增加值占GDP比重数据
  • 网络安全基础
  • Python 调用 C 程序时输出顺序错乱问题分析与解决
  • 0x-2-Oracle Linux 9上安装JDK配置环境变量
  • 第五讲 基础IO
  • Go切片与映射的内存优化技巧:实战经验与最佳实践
  • 【LeetCode】算法详解#6 ---除自身以外数组的乘积
  • JUC并发编程(六)CAS无锁实现/原子整数/原子引用/原子数组/字段更新
  • Python训练营---DAY48
  • Java线程安全与同步机制全解析
  • 嵌入式学习笔记 - freeRTOS为什么中断中不能使用互斥量
  • 《最短路(Dijkstra+Heap)》题集
  • MySql读写分离部署(一主一从,双主双从,Mycat)
  • 为什么已经有 Nginx 了,还需要服务网关?
  • 【LUT技术专题】带语义的图像自适应4DLUT
  • Cherry-Studio搭建个人知识库智能体
  • JS的数据类型分类
  • 国产变频器与codesys控制器通信融合方案
  • gitee....
  • SpringSecurity+vue通用权限系统