C++进阶:(十六)从裸指针到智能指针,C++ 内存管理的 “自动驾驶” 进化之路

C++进阶:(十六)从裸指针到智能指针,C++ 内存管理的 “自动驾驶” 进化之路

目录

前言

一、裸指针的 “血泪史”:为什么我们需要智能指针?

1.1 内存泄漏:最常见的 “噩梦”

1.2 二次释放:致命的 “双重打击”

1.3 野指针:潜伏的 “幽灵”

1.4 异常安全:被忽略的 “隐形杀手”

1.5 智能指针的核心使命

二、智能指针的 “三驾马车”:unique_ptr、shared_ptr、weak_ptr

2.1 unique_ptr:独占所有权的 “独行侠”

2.1.1 unique_ptr 的核心原理

2.1.2 unique_ptr 的基本使用

2.1.3 unique_ptr 的使用场景与最佳实践

2.2 shared_ptr:共享所有权的 “社交达人”

2.2.1 shared_ptr 的核心原理:引用计数

2.2.2 shared_ptr 的基本使用

2.2.3 循环引用:shared_ptr 的 “阿喀琉斯之踵”

2.3 weak_ptr:打破循环的 “旁观者”

2.3.1 weak_ptr 的核心原理

2.3.2 weak_ptr 的基本使用与循环引用解决方案

2.3.3 weak_ptr 的使用场景与最佳实践

三、智能指针的 “进阶技巧”:定制删除器、类型转换与性能优化

3.1 定制删除器:处理特殊资源释放

3.1.1 unique_ptr 的定制删除器

3.1.2 shared_ptr 的定制删除器

3.2 智能指针的类型转换

3.3 智能指针的性能优化

3.3.1 优先使用 unique_ptr

3.3.2 使用 make_shared 减少内存分配

3.3.3 避免不必要的 shared_ptr 拷贝

3.3.4 合理使用 weak_ptr 的 lock ()

四、智能指针的 “避坑指南”:常见错误与最佳实践总结

4.1 常见错误

4.2 最佳实践总结

总结


前言

        在 C++ 的世界里,有一个让无数开发者 “谈虎色变” 的话题 —— 内存管理。指针作为 C++ 的 “灵魂”,赋予了开发者直接操作内存的强大能力,但也埋下了内存泄漏、野指针、二次释放等一系列 “定时炸弹”。你是否也曾因为一个忘记释放的new,调试了一下午的内存泄漏?是否也曾被野指针导致的程序崩溃搞得怀疑人生?

        幸运的是,C++ 标准库为我们提供了一套 “自动驾驶” 级别的解决方案 —— 智能指针。它就像给裸指针装上了 “自动刹车” 和 “自动泊车” 系统,让内存管理变得安全、高效且省心。本文将从底层原理到实际应用,全方位拆解智能指针的设计精髓,带你彻底掌握unique_ptrshared_ptrweak_ptr的使用技巧,从此和内存问题说再见!下面就让我们正式开始吧!

一、裸指针的 “血泪史”:为什么我们需要智能指针?

        在深入智能指针之前,我们先回顾一下裸指针(Raw Pointer)的 “坑”。正是这些痛点,催生了智能指针的诞生。

1.1 内存泄漏:最常见的 “噩梦”

        内存泄漏是指程序分配的内存空间在使用完毕后,没有被正确释放,导致这部分内存永远无法被再次使用。尤其在复杂的程序逻辑中,一个疏忽就可能造成内存泄漏。

// 反面示例:裸指针导致的内存泄漏 void func() { int* p = new int(10); // 分配堆内存 // 业务逻辑处理... if (some_condition) { return; // 提前返回,忘记释放p } // 其他操作... delete p; // 正常路径下的释放,但异常路径会跳过 } int main() { while (true) { func(); // 循环调用,每次都会泄漏4字节内存 sleep(1); } return 0; } 

        在上述代码中,如果some_conditiontrue,函数会提前返回,delete p语句就永远不会执行,导致堆内存泄漏。如果这段代码在循环中执行,内存会持续增长,最终导致程序崩溃。

1.2 二次释放:致命的 “双重打击”

        二次释放是指对同一块内存进行多次delete操作。这会破坏堆内存的完整性,导致程序崩溃或未定义行为。

// 反面示例:裸指针导致的二次释放 void func() { int* p = new int(20); delete p; // 第一次释放 // ... 中间经过复杂的逻辑,忘记p已经被释放 delete p; // 第二次释放,程序崩溃 } 

        这种问题在多人协作或复杂代码中尤为常见 —— 当一个指针被传递到多个函数后,很难追踪它是否已经被释放。

1.3 野指针:潜伏的 “幽灵”

        野指针是指指向已释放内存或非法内存地址的指针。访问野指针会导致程序崩溃、数据损坏等不可预测的结果。

// 反面示例:裸指针导致的野指针问题 int* func() { int x = 10; // 栈内存,函数返回后会被销毁 return &x; // 返回栈内存地址,形成野指针 } int main() { int* p = func(); cout << *p << endl; // 访问野指针,行为未定义(可能输出乱码或崩溃) return 0; } 

        栈内存的生命周期与函数作用域绑定,函数返回后栈内存会被系统回收,此时返回的指针就成了野指针。

1.4 异常安全:被忽略的 “隐形杀手”

        当程序发生异常时,正常的执行流程会被打断,可能导致裸指针无法被释放。

// 反面示例:异常导致的内存泄漏 void func() { int* p = new int(30); try { // 模拟抛出异常 throw runtime_error("something wrong"); } catch (...) { // 未处理p的释放,导致内存泄漏 throw; // 重新抛出异常 } delete p; // 永远不会执行 } 

        即使我们小心谨慎地处理了所有正常路径,异常也可能成为 “漏网之鱼”,导致内存泄漏。

1.5 智能指针的核心使命

        面对裸指针的种种问题,智能指针的核心设计思想应运而生:将指针的生命周期管理与对象的生命周期绑定,通过 RAII(资源获取即初始化)机制,实现内存的自动释放

        简单来说,智能指针是一个 “包装器类”,它封装了裸指针,并在其析构函数中自动执行delete操作。由于 C++ 的对象生命周期遵循 “出作用域即析构” 的规则,当智能指针对象离开作用域时,析构函数会自动调用,从而保证内存被正确释放,从根本上避免了内存泄漏、二次释放等问题。

二、智能指针的 “三驾马车”:unique_ptr、shared_ptr、weak_ptr

        C++11 标准库提供了三种核心智能指针:unique_ptrshared_ptrweak_ptr。它们各自有着不同的设计理念和适用场景,共同构成了 C++ 内存管理的 “主力军”。

2.1 unique_ptr:独占所有权的 “独行侠”

    unique_ptr是最简单、最高效的智能指针,它的核心特性是独占所有权—— 同一时间,只能有一个unique_ptr指向一块内存。当unique_ptr对象被销毁时,它所指向的内存也会被自动释放。

2.1.1 unique_ptr 的核心原理

    unique_ptr的底层实现非常简洁:

封装一个裸指针(T* ptr);禁用拷贝构造函数和拷贝赋值运算符(C++11 中通过= delete实现),确保所有权无法被复制;支持移动构造函数和移动赋值运算符,允许所有权的 “转移”;析构函数中调用delete(或delete[],针对数组类型)释放内存。

        我们可以通过一个简化版的unique_ptr来理解其原理:

// 简化版unique_ptr实现(仅演示核心逻辑) template <typename T> class MyUniquePtr { private: T* ptr; // 封装的裸指针 public: // 构造函数:接收裸指针 explicit MyUniquePtr(T* p = nullptr) : ptr(p) {} // 析构函数:自动释放内存 ~MyUniquePtr() { delete ptr; // 核心:自动delete ptr = nullptr; } // 禁用拷贝构造(独占所有权,不允许复制) MyUniquePtr(const MyUniquePtr& other) = delete; // 禁用拷贝赋值 MyUniquePtr& operator=(const MyUniquePtr& other) = delete; // 移动构造:转移所有权 MyUniquePtr(MyUniquePtr&& other) noexcept : ptr(other.ptr) { other.ptr = nullptr; // 原指针置空,避免二次释放 } // 移动赋值:转移所有权 MyUniquePtr& operator=(MyUniquePtr&& other) noexcept { if (this != &other) { delete ptr; // 释放当前指针指向的内存 ptr = other.ptr; // 接收对方的指针 other.ptr = nullptr; // 原指针置空 } return *this; } // 重载->运算符,支持指针访问语法 T* operator->() const { return ptr; } // 重载*运算符,支持解引用 T& operator*() const { return *ptr; } // 获取裸指针(谨慎使用) T* get() const { return ptr; } // 释放所有权(返回裸指针,智能指针不再管理该内存) T* release() { T* temp = ptr; ptr = nullptr; return temp; } // 重置指针(释放当前内存,指向新地址) void reset(T* p = nullptr) { delete ptr; ptr = p; } }; 

        从简化实现可以看出,unique_ptr通过禁用拷贝、支持移动,确保了所有权的独占性,同时通过析构函数自动释放内存,实现了 “零泄漏” 的保证。

2.1.2 unique_ptr 的基本使用

    unique_ptr的使用非常直观,以下是常见操作示例:

#include <memory> #include <iostream> using namespace std; class Test { public: Test(int id) : id_(id) { cout << "Test(" << id_ << ") 构造" << endl; } ~Test() { cout << "Test(" << id_ << ") 析构" << endl; } void show() { cout << "Test id: " << id_ << endl; } private: int id_; }; // 1. 基本初始化与使用 void test_unique_ptr_basic() { cout << "=== test_unique_ptr_basic ===" << endl; // 方式1:通过make_unique创建(推荐,更安全) unique_ptr<Test> up1 = make_unique<Test>(1); up1->show(); // 调用成员函数 cout << "up1 get: " << up1.get() << endl; // 获取裸指针 // 方式2:通过new创建(不推荐,可能导致内存泄漏) unique_ptr<Test> up2(new Test(2)); up2->show(); // 错误:不允许拷贝构造 // unique_ptr<Test> up3 = up1; // 错误:不允许拷贝赋值 // unique_ptr<Test> up4; // up4 = up1; // 正确:移动构造(转移所有权) unique_ptr<Test> up5 = move(up1); up5->show(); cout << "up1 get after move: " << up1.get() << endl; // up1变为nullptr // 正确:移动赋值(转移所有权) unique_ptr<Test> up6; up6 = move(up2); up6->show(); cout << "up2 get after move: " << up2.get() << endl; // up2变为nullptr // 重置指针(释放当前内存,指向新对象) up5.reset(new Test(5)); up5->show(); // 释放所有权(up6不再管理该内存,需手动释放) Test* raw_ptr = up6.release(); delete raw_ptr; // 必须手动delete,否则内存泄漏 cout << "=== test_unique_ptr_basic end ===" << endl; } // 2. 管理数组(需指定数组类型) void test_unique_ptr_array() { cout << "\n=== test_unique_ptr_array ===" << endl; // 管理int数组(注意模板参数为int[]) unique_ptr<int[]> up_arr(new int[5]{10, 20, 30, 40, 50}); for (int i = 0; i < 5; ++i) { cout << up_arr[i] << " "; // 支持[]运算符 } cout << endl; // 析构时会自动调用delete[],无需手动释放 cout << "=== test_unique_ptr_array end ===" << endl; } // 3. 作为函数返回值(自动移动,无需显式调用move) unique_ptr<Test> create_test(int id) { return make_unique<Test>(id); // 编译器自动优化为移动语义 } void test_unique_ptr_return() { cout << "\n=== test_unique_ptr_return ===" << endl; unique_ptr<Test> up = create_test(10); up->show(); cout << "=== test_unique_ptr_return end ===" << endl; } int main() { test_unique_ptr_basic(); test_unique_ptr_array(); test_unique_ptr_return(); return 0; } 

        运行结果如下:

=== test_unique_ptr_basic === Test(1) 构造 Test id: 1 up1 get: 0x7f8b4a405a00 Test(2) 构造 Test id: 2 Test id: 1 up1 get after move: 0x0 Test id: 2 up2 get after move: 0x0 Test(5) 构造 Test id: 5 Test(2) 析构 Test(5) 析构 === test_unique_ptr_basic end === === test_unique_ptr_array === 10 20 30 40 50 === test_unique_ptr_array end === === test_unique_ptr_return === Test(10) 构造 Test id: 10 === test_unique_ptr_return end === Test(10) 析构 

        从运行结果可以看出:

  • unique_ptr对象离开作用域时,所指向的Test对象会自动析构;
  • 移动操作后,原unique_ptr会被置空,避免二次释放;
  • 管理数组时,析构函数会自动调用delete[],无需手动处理。

2.1.3 unique_ptr 的使用场景与最佳实践

    unique_ptr是最常用的智能指针,适用于以下场景:

独占资源所有权:当一块内存只需要被一个指针管理时,优先使用unique_ptr作为函数参数 / 返回值:传递临时对象的所有权(通过移动语义);管理局部动态对象:替代裸指针,避免函数退出时忘记释放内存;容器元素vector<unique_ptr<T>>是常见用法,避免容器元素的拷贝开销。

        最佳实践:

优先使用make_unique创建unique_ptrmake_unique是 C++14 引入的函数,它能避免直接使用new,减少内存泄漏风险(例如make_unique<Test>(1)unique_ptr<Test>(new Test(1))更安全);避免手动调用get()release():这些函数会暴露裸指针,可能破坏unique_ptr的所有权管理,仅在必要时使用;管理数组时指定数组类型:使用unique_ptr<T[]>而非unique_ptr<T>,确保析构时调用delete[]

2.2 shared_ptr:共享所有权的 “社交达人”

    unique_ptr的独占性虽然高效,但无法满足 “多个指针共享同一块内存” 的场景(例如,多个对象需要引用同一个资源)。此时,shared_ptr应运而生 —— 它支持共享所有权,多个shared_ptr可以指向同一块内存,当最后一个shared_ptr被销毁时,内存才会被释放。

2.2.1 shared_ptr 的核心原理:引用计数

    shared_ptr的核心机制是引用计数(Reference Counting)

每个shared_ptr都封装了一个 “数据指针”(指向实际对象)和一个 “控制块指针”(指向控制块);控制块中存储了引用计数(当前指向该对象的shared_ptr数量)、弱引用计数(当前指向该对象的weak_ptr数量)以及对象的析构器等信息;当创建一个新的shared_ptr指向对象时,引用计数加 1;当shared_ptr被销毁(析构)或指向其他对象(赋值)时,引用计数减 1;当引用计数减为 0 时,控制块会调用析构器释放对象内存,随后释放控制块本身。

·        我们可以通过简化版的shared_ptr理解其原理:

// 简化版shared_ptr实现(仅演示核心逻辑) template <typename T> class MySharedPtr { private: T* data_ptr; // 指向实际对象的指针 struct ControlBlock { int ref_count; // 引用计数 int weak_count; // 弱引用计数(为weak_ptr预留) T* obj_ptr; // 指向对象的指针 ControlBlock(T* p) : ref_count(1), weak_count(0), obj_ptr(p) {} ~ControlBlock() { delete obj_ptr; // 释放对象内存 } }; ControlBlock* control_block; // 指向控制块的指针 // 减少引用计数,必要时释放控制块 void decrement_ref_count() { if (control_block) { control_block->ref_count--; // 引用计数为0,且弱引用计数也为0时,释放控制块 if (control_block->ref_count == 0) { if (control_block->weak_count == 0) { delete control_block; } else { // 弱引用存在时,仅释放对象,不释放控制块 control_block->obj_ptr = nullptr; } } control_block = nullptr; data_ptr = nullptr; } } public: // 构造函数:创建新的控制块 explicit MySharedPtr(T* p = nullptr) : data_ptr(p) { if (p) { control_block = new ControlBlock(p); } else { control_block = nullptr; } } // 析构函数:减少引用计数 ~MySharedPtr() { decrement_ref_count(); } // 拷贝构造函数:共享控制块,引用计数加1 MySharedPtr(const MySharedPtr& other) { data_ptr = other.data_ptr; control_block = other.control_block; if (control_block) { control_block->ref_count++; } } // 拷贝赋值运算符:先减少当前引用计数,再共享新的控制块 MySharedPtr& operator=(const MySharedPtr& other) { if (this != &other) { decrement_ref_count(); // 释放当前资源 data_ptr = other.data_ptr; control_block = other.control_block; if (control_block) { control_block->ref_count++; } } return *this; } // 移动构造函数:转移所有权,不修改引用计数 MySharedPtr(MySharedPtr&& other) noexcept { data_ptr = other.data_ptr; control_block = other.control_block; other.data_ptr = nullptr; other.control_block = nullptr; } // 移动赋值运算符:转移所有权 MySharedPtr& operator=(MySharedPtr&& other) noexcept { if (this != &other) { decrement_ref_count(); data_ptr = other.data_ptr; control_block = other.control_block; other.data_ptr = nullptr; other.control_block = nullptr; } return *this; } // 重载->和*运算符 T* operator->() const { return data_ptr; } T& operator*() const { return *data_ptr; } // 获取引用计数 int use_count() const { return control_block ? control_block->ref_count : 0; } // 获取裸指针 T* get() const { return data_ptr; } }; 

        从简化实现可以看出,shared_ptr的核心是控制块和引用计数。多个shared_ptr通过共享同一个控制块,实现了引用计数的同步更新,从而保证了共享所有权的正确性。

2.2.2 shared_ptr 的基本使用

    shared_ptr的使用方式与unique_ptr类似,但支持拷贝操作。以下是常见操作示例:

#include <memory> #include <iostream> using namespace std; class Test { public: Test(int id) : id_(id) { cout << "Test(" << id_ << ") 构造" << endl; } ~Test() { cout << "Test(" << id_ << ") 析构" << endl; } void show() { cout << "Test id: " << id_ << ", use_count: " << sp_self->use_count() << endl; } // 持有自身的shared_ptr(用于演示循环引用) shared_ptr<Test> sp_self; private: int id_; }; // 1. 基本初始化与拷贝 void test_shared_ptr_basic() { cout << "=== test_shared_ptr_basic ===" << endl; // 方式1:通过make_shared创建(推荐) shared_ptr<Test> sp1 = make_shared<Test>(1); cout << "sp1 use_count: " << sp1.use_count() << endl; // 1 // 拷贝构造:引用计数加1 shared_ptr<Test> sp2 = sp1; cout << "sp1 use_count after copy: " << sp1.use_count() << endl; // 2 cout << "sp2 use_count after copy: " << sp2.use_count() << endl; // 2 // 拷贝赋值:引用计数加1 shared_ptr<Test> sp3; sp3 = sp1; cout << "sp1 use_count after assign: " << sp1.use_count() << endl; // 3 // 移动构造:引用计数不变 shared_ptr<Test> sp4 = move(sp1); cout << "sp1 use_count after move: " << sp1.use_count() << endl; // 0(sp1已置空) cout << "sp4 use_count after move: " << sp4.use_count() << endl; // 2(sp2和sp3仍在) // 重置指针:引用计数减1 sp2.reset(); cout << "sp2 reset, sp3 use_count: " << sp3.use_count() << endl; // 1(仅sp3和sp4) sp3.reset(new Test(2)); cout << "sp3 reset to new Test, use_count: " << sp3.use_count() << endl; // 1 cout << "=== test_shared_ptr_basic end ===" << endl; // sp4、sp3离开作用域,Test(1)和Test(2)会被析构 } // 2. 共享所有权的实际场景 void test_shared_ptr_share() { cout << "\n=== test_shared_ptr_share ===" << endl; shared_ptr<Test> sp = make_shared<Test>(10); // 多个函数共享同一个Test对象 auto func1 = [&]() { shared_ptr<Test> sp1 = sp; cout << "func1: "; sp1->show(); }; auto func2 = [&]() { shared_ptr<Test> sp2 = sp; cout << "func2: "; sp2->show(); }; func1(); func2(); cout << "main: sp use_count: " << sp.use_count() << endl; // 1(func1和func2的sp1、sp2已析构) cout << "=== test_shared_ptr_share end ===" << endl; } // 3. 循环引用问题(shared_ptr的致命缺陷) void test_shared_ptr_cycle() { cout << "\n=== test_shared_ptr_cycle ===" << endl; shared_ptr<Test> sp1 = make_shared<Test>(100); shared_ptr<Test> sp2 = make_shared<Test>(200); // 循环引用:sp1持有sp2,sp2持有sp1 sp1->sp_self = sp2; sp2->sp_self = sp1; cout << "sp1 use_count: " << sp1.use_count() << endl; // 2(sp1和sp2->sp_self) cout << "sp2 use_count: " << sp2.use_count() << endl; // 2(sp2和sp1->sp_self) // sp1和sp2离开作用域时,引用计数减为1(而非0),导致Test对象无法析构 cout << "=== test_shared_ptr_cycle end ===" << endl; } int main() { test_shared_ptr_basic(); test_shared_ptr_share(); test_shared_ptr_cycle(); return 0; } 

        运行结果如下:

=== test_shared_ptr_basic === Test(1) 构造 sp1 use_count: 1 sp1 use_count after copy: 2 sp2 use_count after copy: 2 sp1 use_count after assign: 3 sp1 use_count after move: 0 sp4 use_count after move: 2 sp2 reset, sp3 use_count: 1 Test(2) 构造 sp3 reset to new Test, use_count: 1 === test_shared_ptr_basic end === Test(1) 析构 Test(2) 析构 === test_shared_ptr_share === Test(10) 构造 func1: Test id: 10, use_count: 2 func2: Test id: 10, use_count: 2 main: sp use_count: 1 === test_shared_ptr_share end === Test(10) 析构 === test_shared_ptr_cycle === Test(100) 构造 Test(200) 构造 sp1 use_count: 2 sp2 use_count: 2 === test_shared_ptr_cycle end === 

        从运行结果可以发现一个关键问题:test_shared_ptr_cycle函数结束后,Test(100)Test(200)并没有被析构!这就是shared_ptr的致命缺陷 ——循环引用

2.2.3 循环引用:shared_ptr 的 “阿喀琉斯之踵”

        循环引用是指两个或多个shared_ptr互相持有对方的引用,导致它们的引用计数永远无法减为 0,从而造成内存泄漏。

        在test_shared_ptr_cycle中:

sp1指向Test(100)sp2指向Test(200),初始引用计数均为 1;sp1->sp_self = sp2Test(100)sp_self持有sp2sp2的引用计数变为 2;sp2->sp_self = sp1Test(200)sp_self持有sp1sp1的引用计数变为 2;函数结束时,sp1sp2离开作用域,它们的引用计数各减 1,变为 1;此时,Test(100)sp_self仍持有sp2Test(200)sp_self仍持有sp1,引用计数无法减为 0,两个Test对象永远无法析构,造成内存泄漏。

        为了解决循环引用问题,C++ 标准库引入了第三种智能指针 ——weak_ptr

2.3 weak_ptr:打破循环的 “旁观者”

    weak_ptr是一种 “弱引用” 智能指针,它的核心特性是不拥有对象的所有权,仅能观察shared_ptr所管理的对象。weak_ptr不会增加shared_ptr的引用计数,因此不会导致循环引用问题。

2.3.1 weak_ptr 的核心原理

    weak_ptr的底层依赖shared_ptr的控制块:

weak_ptr仅存储控制块的指针,不存储数据指针;创建weak_ptr时,会将控制块的弱引用计数加 1;weak_ptr无法直接访问对象,必须通过lock()方法获取一个shared_ptr(此时引用计数加 1),才能访问对象;当shared_ptr的引用计数减为 0 时,对象会被析构,但控制块会保留到弱引用计数也为 0 时才释放;weak_ptr可以通过expired()方法判断所观察的对象是否已被析构。

        我们可以通过简化版的weak_ptr理解其原理:

// 简化版weak_ptr实现(与MySharedPtr配套) template <typename T> class MyWeakPtr { private: typename MySharedPtr<T>::ControlBlock* control_block; // 仅指向控制块 public: // 默认构造 MyWeakPtr() : control_block(nullptr) {} // 从shared_ptr构造:弱引用计数加1 MyWeakPtr(const MySharedPtr<T>& sp) { control_block = sp.control_block; if (control_block) { control_block->weak_count++; } } // 拷贝构造 MyWeakPtr(const MyWeakPtr& other) { control_block = other.control_block; if (control_block) { control_block->weak_count++; } } // 析构函数:弱引用计数减1 ~MyWeakPtr() { if (control_block) { control_block->weak_count--; // 引用计数和弱引用计数均为0时,释放控制块 if (control_block->ref_count == 0 && control_block->weak_count == 0) { delete control_block; } control_block = nullptr; } } // 拷贝赋值 MyWeakPtr& operator=(const MyWeakPtr& other) { if (this != &other) { // 释放当前弱引用 if (control_block) { control_block->weak_count--; if (control_block->ref_count == 0 && control_block->weak_count == 0) { delete control_block; } } // 指向新的控制块 control_block = other.control_block; if (control_block) { control_block->weak_count++; } } return *this; } // 从shared_ptr赋值 MyWeakPtr& operator=(const MySharedPtr<T>& sp) { // 释放当前弱引用 if (control_block) { control_block->weak_count--; if (control_block->ref_count == 0 && control_block->weak_count == 0) { delete control_block; } } // 指向sp的控制块 control_block = sp.control_block; if (control_block) { control_block->weak_count++; } return *this; } // 检查对象是否已过期(被析构) bool expired() const { return !control_block || control_block->ref_count == 0; } // 获取shared_ptr,用于访问对象 MySharedPtr<T> lock() const { if (expired()) { return MySharedPtr<T>(nullptr); // 对象已过期,返回空shared_ptr } // 引用计数加1,返回shared_ptr return MySharedPtr<T>(control_block->obj_ptr, control_block); } // 获取弱引用计数 int use_count() const { return control_block ? control_block->ref_count : 0; } }; 

        从简化实现可以看出,weak_ptr不直接管理对象内存,仅通过控制块的弱引用计数跟踪shared_ptr的状态。通过lock()方法,weak_ptr可以安全地获取shared_ptr来访问对象,避免了悬空指针问题。

2.3.2 weak_ptr 的基本使用与循环引用解决方案

    weak_ptr不能单独使用,必须与shared_ptr配合。以下是其常见用法,重点演示如何解决循环引用问题:

#include <memory> #include <iostream> using namespace std; class Test { public: Test(int id) : id_(id) { cout << "Test(" << id_ << ") 构造" << endl; } ~Test() { cout << "Test(" << id_ << ") 析构" << endl; } void show() { cout << "Test id: " << id_ << endl; } // 将shared_ptr改为weak_ptr,打破循环引用 weak_ptr<Test> wp_self; private: int id_; }; // 1. weak_ptr的基本操作 void test_weak_ptr_basic() { cout << "=== test_weak_ptr_basic ===" << endl; shared_ptr<Test> sp = make_shared<Test>(1); // 从shared_ptr创建weak_ptr weak_ptr<Test> wp = sp; cout << "wp use_count: " << wp.use_count() << endl; // 1(引用计数,weak_ptr不影响) cout << "wp expired: " << wp.expired() << endl; // false(对象未析构) // 通过lock()获取shared_ptr,访问对象 if (auto sp1 = wp.lock()) { cout << "lock success: "; sp1->show(); cout << "sp1 use_count: " << sp1.use_count() << endl; // 2(sp和sp1) } else { cout << "object has been destroyed" << endl; } // 重置shared_ptr,对象析构 sp.reset(); cout << "sp reset, wp expired: " << wp.expired() << endl; // true(对象已析构) // 再次lock(),返回空shared_ptr if (auto sp2 = wp.lock()) { sp2->show(); } else { cout << "lock failed: object has been destroyed" << endl; } cout << "=== test_weak_ptr_basic end ===" << endl; } // 2. 解决循环引用问题 void test_weak_ptr_solve_cycle() { cout << "\n=== test_weak_ptr_solve_cycle ===" << endl; shared_ptr<Test> sp1 = make_shared<Test>(100); shared_ptr<Test> sp2 = make_shared<Test>(200); // 用weak_ptr代替shared_ptr,避免循环引用 sp1->wp_self = sp2; // wp_self是weak_ptr,不增加sp2的引用计数 sp2->wp_self = sp1; // 同理,不增加sp1的引用计数 cout << "sp1 use_count: " << sp1.use_count() << endl; // 1(仅sp1) cout << "sp2 use_count: " << sp2.use_count() << endl; // 1(仅sp2) // 通过lock()访问对方 if (auto sp = sp1->wp_self.lock()) { cout << "sp1 access sp2: "; sp->show(); } cout << "=== test_weak_ptr_solve_cycle end ===" << endl; // sp1和sp2离开作用域,引用计数减为0,Test对象被析构 } // 3. weak_ptr的其他应用场景:观察者模式 class Subject; // 前向声明 // 观察者类(弱引用主题,避免循环引用) class Observer { public: Observer(const shared_ptr<Subject>& subject) : wp_subject(subject) {} void update() { if (auto sp_subject = wp_subject.lock()) { cout << "Observer update: subject state = " << sp_subject->getState() << endl; } else { cout << "Subject has been destroyed" << endl; } } private: weak_ptr<Subject> wp_subject; // 弱引用主题 }; // 主题类(持有观察者的shared_ptr) class Subject { public: void setState(int state) { state_ = state; notifyObservers(); } int getState() const { return state_; } void addObserver(const shared_ptr<Observer>& observer) { observers_.push_back(observer); } private: void notifyObservers() { for (auto& observer : observers_) { observer->update(); } } int state_; vector<shared_ptr<Observer>> observers_; }; void test_weak_ptr_observer() { cout << "\n=== test_weak_ptr_observer ===" << endl; shared_ptr<Subject> subject = make_shared<Subject>(); // 创建观察者(持有主题的弱引用) shared_ptr<Observer> observer1 = make_shared<Observer>(subject); shared_ptr<Observer> observer2 = make_shared<Observer>(subject); subject->addObserver(observer1); subject->addObserver(observer2); // 主题状态变化,通知观察者 subject->setState(10); subject->setState(20); // 主题被销毁 subject.reset(); cout << "After subject reset:" << endl; observer1->update(); // 输出"Subject has been destroyed" cout << "=== test_weak_ptr_observer end ===" << endl; } int main() { test_weak_ptr_basic(); test_weak_ptr_solve_cycle(); test_weak_ptr_observer(); return 0; } 

        运行结果如下:

=== test_weak_ptr_basic === Test(1) 构造 wp use_count: 1 wp expired: 0 lock success: Test id: 1 sp1 use_count: 2 sp reset, wp expired: 1 lock failed: object has been destroyed Test(1) 析构 === test_weak_ptr_basic end === === test_weak_ptr_solve_cycle === Test(100) 构造 Test(200) 构造 sp1 use_count: 1 sp2 use_count: 1 sp1 access sp2: Test id: 200 === test_weak_ptr_solve_cycle end === Test(200) 析构 Test(100) 析构 === test_weak_ptr_observer === Observer update: subject state = 10 Observer update: subject state = 10 Observer update: subject state = 20 Observer update: subject state = 20 After subject reset: Subject has been destroyed === test_weak_ptr_observer end === 

        从运行结果可以看出:

weak_ptr不会增加shared_ptr的引用计数,因此不会导致循环引用;通过lock()方法可以安全地获取shared_ptr,访问对象前会检查对象是否已析构;在观察者模式中,weak_ptr可以避免观察者和主题之间的循环引用,同时允许主题被正常销毁。

2.3.3 weak_ptr 的使用场景与最佳实践

    weak_ptr的使用场景主要包括:

解决shared_ptr的循环引用问题:这是weak_ptr最核心的用途,将循环引用中的一方改为weak_ptr即可;观察者模式:观察者持有主题的weak_ptr,避免主题被观察者 “绑架”(即使有观察者,主题也能正常销毁);缓存场景:缓存对象的weak_ptr,当对象被销毁时,缓存自动失效,避免悬空指针。

        最佳实践:

不单独使用weak_ptrweak_ptr必须与shared_ptr配合使用,无法直接访问对象;使用lock()前先检查expired():虽然lock()会返回空指针,但提前检查expired()可以提高代码可读性;避免长期持有lock()返回的shared_ptrlock()返回的shared_ptr会增加引用计数,长期持有会导致对象无法及时析构。

三、智能指针的 “进阶技巧”:定制删除器、类型转换与性能优化

        除了基本用法,智能指针还有一些进阶技巧,可以应对更复杂的场景。

3.1 定制删除器:处理特殊资源释放

        默认情况下,智能指针会使用delete(或delete[])释放内存。但在某些场景下,我们需要自定义释放逻辑(例如,释放动态数组、文件句柄、网络连接等),此时可以使用 “定制删除器”。

3.1.1 unique_ptr 的定制删除器

    unique_ptr的定制删除器是模板参数的一部分,语法如下:

#include <memory> #include <iostream> #include <fstream> using namespace std; // 1. 释放动态数组(unique_ptr<T[]>已支持,此处仅为示例) void delete_array(int* p) { cout << "Custom deleter: delete[] array" << endl; delete[] p; } // 2. 释放文件句柄 void close_file(FILE* fp) { if (fp) { cout << "Custom deleter: close file" << endl; fclose(fp); } } // 3. lambda表达式作为删除器(更简洁) auto delete_lambda = [](Test* p) { cout << "Custom deleter (lambda): delete Test(" << p->id_ << ")" << endl; delete p; }; void test_custom_deleter_unique() { cout << "=== test_custom_deleter_unique ===" << endl; // 1. 函数指针作为删除器 unique_ptr<int, decltype(&delete_array)> up1(new int[5]{1,2,3,4,5}, delete_array); // 2. 释放文件句柄 FILE* fp = fopen("test.txt", "w"); unique_ptr<FILE, decltype(&close_file)> up2(fp, close_file); // 3. lambda表达式作为删除器 unique_ptr<Test, decltype(delete_lambda)> up3(new Test(10), delete_lambda); cout << "=== test_custom_deleter_unique end ===" << endl; } 

        运行结果:

=== test_custom_deleter_unique === Test(10) 构造 === test_custom_deleter_unique end === Custom deleter (lambda): delete Test(10) Custom deleter: close file Custom deleter: delete[] array 

3.1.2 shared_ptr 的定制删除器

    shared_ptr的定制删除器是构造函数的参数,语法更灵活

void test_custom_deleter_shared() { cout << "\n=== test_custom_deleter_shared ===" << endl; // 1. 函数指针作为删除器 shared_ptr<int> sp1(new int[5]{1,2,3,4,5}, [](int* p) { cout << "Custom deleter (shared): delete[] array" << endl; delete[] p; }); // 2. 释放文件句柄 FILE* fp = fopen("test.txt", "w"); shared_ptr<FILE> sp2(fp, close_file); // 3. 成员函数作为删除器 class FileDeleter { public: void operator()(FILE* fp) { if (fp) { cout << "Custom deleter (member function): close file" << endl; fclose(fp); } } }; shared_ptr<FILE> sp3(fopen("test2.txt", "w"), FileDeleter()); cout << "=== test_custom_deleter_shared end ===" << endl; } 

        运行结果:

=== test_custom_deleter_shared === === test_custom_deleter_shared end === Custom deleter (shared): delete[] array Custom deleter: close file Custom deleter (member function): close file 

3.2 智能指针的类型转换

        裸指针可以通过static_castdynamic_cast等进行类型转换,但智能指针不能直接使用这些运算符。C++ 标准库提供了专门的类型转换函数:

static_pointer_cast:对应static_cast,用于静态类型转换;dynamic_pointer_cast:对应dynamic_cast,用于动态类型转换(支持运行时类型检查);const_pointer_cast:对应const_cast,用于去除const限定。

        示例代码:

#include <memory> #include <iostream> using namespace std; class Base { public: virtual void show() { cout << "Base show" << endl; } virtual ~Base() {} }; class Derived : public Base { public: void show() override { cout << "Derived show" << endl; } void derived_func() { cout << "Derived specific function" << endl; } }; void test_pointer_cast() { cout << "=== test_pointer_cast ===" << endl; // 1. dynamic_pointer_cast(动态类型转换) shared_ptr<Base> sp_base = make_shared<Derived>(); if (auto sp_derived = dynamic_pointer_cast<Derived>(sp_base)) { sp_derived->show(); // Derived show sp_derived->derived_func(); // Derived specific function } else { cout << "dynamic_cast failed" << endl; } // 2. static_pointer_cast(静态类型转换) shared_ptr<Base> sp_base2 = static_pointer_cast<Base>(make_shared<Derived>()); sp_base2->show(); // Derived show // 3. const_pointer_cast(去除const限定) shared_ptr<const Base> sp_const = make_shared<Base>(); auto sp_non_const = const_pointer_cast<Base>(sp_const); sp_non_const->show(); // Base show cout << "=== test_pointer_cast end ===" << endl; } int main() { test_pointer_cast(); return 0; } 

        运行结果:

=== test_pointer_cast === Derived show Derived specific function Derived show Base show === test_pointer_cast end === 

        注意:

dynamic_pointer_cast仅对多态类型(含有虚函数)有效,非多态类型会编译失败;类型转换仅适用于shared_ptrunique_ptr不支持(因为unique_ptr的所有权独占,转换会破坏安全性)。

3.3 智能指针的性能优化

        智能指针的性能开销主要来自两个方面:

shared_ptr引用计数操作(原子操作,有一定开销);控制块的内存分配(shared_ptr的控制块需要额外分配内存)。

3.3.1 优先使用 unique_ptr

    unique_ptr的性能几乎与裸指针一致(无额外开销),因此在不需要共享所有权时,优先使用unique_ptr,而非shared_ptr

3.3.2 使用 make_shared 减少内存分配

    make_shared会一次性分配对象和控制块的内存,而shared_ptr<T>(new T())会分两次分配(一次分配对象,一次分配控制块)。因此,make_shared不仅更安全,还能减少内存分配次数,提高性能。

// 推荐:一次内存分配(对象+控制块) auto sp1 = make_shared<Test>(1); // 不推荐:两次内存分配(对象和控制块分开) auto sp2 = shared_ptr<Test>(new Test(2)); 

3.3.3 避免不必要的 shared_ptr 拷贝

    shared_ptr的拷贝会增加引用计数(原子操作),频繁拷贝会影响性能。如果仅需要临时访问对象,可通过const&传递shared_ptr,避免拷贝。

// 推荐:传递const引用,避免拷贝 void func(const shared_ptr<Test>& sp) { sp->show(); } // 不推荐:拷贝shared_ptr,增加引用计数 void func(shared_ptr<Test> sp) { sp->show(); } 

3.3.4 合理使用 weak_ptr 的 lock ()

    weak_ptr::lock()会返回shared_ptr,增加引用计数。如果仅需要短暂访问对象,应尽快释放lock()返回的shared_ptr,避免引用计数长期不为 0。

// 推荐:短暂持有lock()返回的shared_ptr if (auto sp = wp.lock()) { sp->show(); } // sp离开作用域,引用计数减1 // 不推荐:长期持有 auto sp = wp.lock(); // 长时间操作... sp->show();

四、智能指针的 “避坑指南”:常见错误与最佳实践总结

        智能指针虽然强大,但如果使用不当,仍可能导致内存问题。以下为大家总结一下常见错误和最佳实践。

4.1 常见错误

  1. shared_ptr的循环引用:导致内存泄漏(已通过weak_ptr解决)。

weak_ptr访问已过期的对象:未检查expired()lock()返回的shared_ptr是否为空,导致访问空指针。

// 错误 weak_ptr<int> wp; { auto sp = make_shared<int>(10); wp = sp; } *wp.lock(); // 错误:wp已过期,lock()返回空指针,解引用崩溃 

管理数组时使用unique_ptr<T>而非unique_ptr<T[]>:导致析构时调用delete而非delete[],造成内存泄漏。

// 错误 unique_ptr<int> up(new int[5]); // 析构时调用delete,内存泄漏 // 正确 unique_ptr<int[]> up(new int[5]); // 析构时调用delete[] 

unique_ptr的拷贝操作unique_ptr禁用拷贝,强行拷贝会编译失败。

// 错误 unique_ptr<int> up1 = make_unique<int>(10); unique_ptr<int> up2 = up1; // 编译失败:拷贝构造被禁用 

使用get()返回的裸指针创建智能指针:同样导致二次释放。

// 错误 shared_ptr<int> sp = make_shared<int>(10); int* p = sp.get(); shared_ptr<int> sp2(p); // 错误:sp和sp2都管理p,会二次释放 

将裸指针交给多个智能指针管理:导致二次释放。

// 错误 int* p = new int(10); unique_ptr<int> up(p); shared_ptr<int> sp(p); // 错误:p被up和sp同时管理,会二次释放 

4.2 最佳实践总结

  1. 优先使用智能指针,替代裸指针:除了极少数场景(如与 C 语言兼容),应尽量使用智能指针管理动态内存;
  2. 优先使用make_sharedmake_unique:避免直接使用new,减少内存泄漏风险,提高性能;
  3. 不混合使用裸指针和智能指针:避免二次释放和内存泄漏;
  4. 避免get()release()的滥用:仅在必要时使用,使用后确保裸指针的生命周期不超过智能指针;
  5. shared_ptr避免循环引用:将循环引用中的一方改为weak_ptr
  6. unique_ptr管理数组时指定T[]:确保析构时调用delete[]
  7. weak_ptr访问对象前检查有效性:通过expired()lock()返回的shared_ptr是否为空判断。

选择合适的智能指针

独占所有权unique_ptr(优先选择,性能最优);共享所有权shared_ptr(配合weak_ptr解决循环引用);观察对象weak_ptr(不拥有所有权,解决循环引用);

总结

        从裸指针的 “步步惊心” 到智能指针的 “自动驾驶”,C++ 的内存管理经历了一次革命性的进化。智能指针通过 RAII 机制,将内存管理的责任从开发者转移到编译器,从根本上解决了内存泄漏、二次释放、野指针等经典问题。

        掌握智能指针的使用及其原理,不仅能让你的代码更安全、更高效,还能让你深刻理解 C++ 的 RAII 设计思想。在实际开发中,应遵循 “能⽤智能指针就不用裸指针” 的原则,合理选择合适的智能指针,让内存管理不再成为你的 “痛点”,而是你的 “底气”。

        最后,希望大家能记住一句话:智能指针不是银弹,但它是 C++ 内存管理最强大的武器。用好它,你就能在 C++ 的世界里 “畅行无阻” !

Read more

Linux --- 泰山派RK3566驱动开发 --- 环境搭建+内核编译

Linux --- 泰山派RK3566驱动开发 --- 环境搭建+内核编译

目录 前言 一、获取官方资料 二、环境准备 1.内核源码获取及编译 1.0 源码获取 1.1 配置交叉编译环境 1.2 编译内核(至少编译一次) 2.完成一个驱动 2.0 第一个无硬件的驱动 2.1 部署到板卡         前言         早些时候,我拿到了泰山派2+16G版本,中间学习了一些相关应用,用2K0300做了车赛,最近才开始正式使用这块板子,拿来学习驱动开发。         官方资料站:立创开发板技术文档中心 一、获取官方资料         开发驱动我们需要完整的软硬件资料才行,立创官方则是提供了完整的资料。         - 本次板子上是Ubuntu系统         - 使用WSL2作为开发机,Ubuntu18.04 二、环境准备 1.内核源码获取及编译

By Ne0inhk
VMware Fusion Pro/Player 在 macOS 上的完整安装与使用指南

VMware Fusion Pro/Player 在 macOS 上的完整安装与使用指南

VMware Fusion Pro/Player 在 macOS 上的完整安装与使用指南—目录 * 一、VMware 产品说明 * 二、下载 VMware Fusion * 三、安装前准备 * 四、安装 VMware Fusion * 步骤 1:安装程序 * 步骤 2:首次启动配置 * 步骤 3:输入许可证 * 五、创建虚拟机 * 步骤 1:新建虚拟机向导 * 步骤 2:选择客户机操作系统 * 步骤 3:分配资源 * 步骤 4:网络配置 * 步骤 5:完成创建 * 六、安装客户机操作系统 * 七、

By Ne0inhk
Flutter 三方库 deepgram_speech_to_text 接驳云生态鸿蒙智能语音感知交互适配:直击泛听写神经网络搭建零延迟强降噪精准指令识别通道-适配鸿蒙 HarmonyOS ohos

Flutter 三方库 deepgram_speech_to_text 接驳云生态鸿蒙智能语音感知交互适配:直击泛听写神经网络搭建零延迟强降噪精准指令识别通道-适配鸿蒙 HarmonyOS ohos

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 deepgram_speech_to_text 接驳云生态鸿蒙智能语音感知交互适配:直击云端最强泛听写神经网络搭建零延迟强降噪精准指令识别通道 前言 在 OpenHarmony 的智慧助手、同声传译及残障辅助类应用中,高性能的语音转文字(STT)是实现自然交互的核心。Deepgram 作为全球顶级的 ASR(Automatic Speech Recognition)服务,其针对实时流式语音的极速响应能力令人惊艳。deepgram_speech_to_text 库为 Flutter 开发者提供了与 Deepgram 云端引擎高效对接的途径。本文将深度剖析如何在鸿蒙端适配该库,利用鸿蒙底层的音频采集技术与云端大脑,构建“听得懂、断句快”的高智能交互终端。 一、原理解析 / 概念介绍 1.1 基础原理/

By Ne0inhk
【Linux】常用基本指令

【Linux】常用基本指令

理论概念 Linux是一款生态功能强大的开源操作系统,因为开源其具有稳定,安全,快速,成本低的优点,更多被专业的人所使用和认可,市场占有率稳步提升 对操作系统的理解: 1.操作系统是层状结构,操作系统是一款做软硬件管理的软件,核心工作是通过对下管理好软硬件资源的手段,达到对上(应用软件)提供良好的(稳定,快速,安全)的操作环境。 2.我们所有的软件行为,都和操作系统直接或间接相关,全都要自顶向下贯穿计算机的软硬件结构 与windows对比: 1.windows也是命令行操作但有图形化界面,主要提供给普通人使用;Linux是纯命令行,虽然也有图形化界面但一般不常用。两者的命令行操作都有对应关系 2.无论是图形化还是指令本质都一样,但指令更贴近操作系统 3.计算机产生的第一需求就是输入输出操作,所以先有指令后有图形,跟硬件产生也有关系,先有键盘后有鼠标 可多人共享一台云服务器 在xshell上可新增用户,并全权管理 指令与命令的区别 指令:直接对应计算机硬件执行的操作,是机器语言中的基本操作单元。 命令:是用户在命令行界面中输入的高级操作请求,通常由 shell

By Ne0inhk