《Eigen入门》第 11 章 · 动态内存与智能指针
之前我们用变量和数组存数据大小都是固定的。但很多时候数据大小要运行时才知道。本章讲如何在堆上动态分配内存以及如何用智能指针避免内存泄漏。11.1 栈和堆内存的两块区域程序运行时内存分两大块区域特点谁管理栈stack自动分配释放、快、大小固定编译器自动堆heap手动分配释放、灵活、大小运行时定程序员手动inta10;// 在栈上出了作用域自动销毁之前所有变量都在栈上自动管理很方便。但栈上数组大小必须编译期确定intn;std::cinn;intarr[n];// ❌ 标准 C 不允许这叫变长数组不是标准的一部分这一条要特别小心因为它可能在你机器上能编译实测 MinGW g 16.1 默认接受这种写法那是 GCC 的扩展只有加-pedantic-errors才拒绝而 MSVC 直接报错。也就是说同一份代码换个编译器就崩而且n很大时会把栈撑爆。大小要到运行期才确定就老老实实用std::vectorint第 12 章或下面的new int[n]。当大小要运行时才确定时就要用堆。11.2 new 和 delete手动分配释放用new在堆上分配内存用delete释放int*pnewint;// 在堆上分配一个 int返回地址*p42;std::cout*pstd::endl;// 42deletep;// 用完释放// 动态数组intn5;int*arrnewint[n];// 在堆上分配 n 个 intarr[0]10;delete[]arr;// 数组要用 delete[]关键规则new分配的必须delete释放new int[n]分配数组用delete[]释放忘了 delete 就会内存泄漏内存被占用但再也收不回11.3 内存泄漏忘 delete 的后果voidleak(){int*pnewint[1000000];// 分配了 100 万个 int// 忘了 delete}// 函数结束指针 p 销毁了但那块内存永远没人释放了如果这个函数被调用很多次内存会越用越少程序越来越卡最终崩溃。这就是内存泄漏——C 最经典的坑。教训手动new/delete太容易出错。所以现代 C 强烈推荐用智能指针自动管理。11.4 智能指针自动释放智能指针是标准库提供的会自己 delete 的指针。它出了作用域自动释放内存你不用手动delete。unique_ptr独占所有权std::unique_ptr保证一块内存只有一个指针拥有它销毁时自动释放#includememory// 智能指针头文件#includeiostreamintmain(){std::unique_ptrintpstd::make_uniqueint(42);// 分配并初始化std::cout*pstd::endl;// 42// 不用 deletep 出了作用域自动释放return0;}make_uniqueT(参数)创建并返回一个 unique_ptr。它不能被复制独占但可以转移所有权。shared_ptr共享所有权std::shared_ptr允许多个指针共享同一块内存内部计数最后一个销毁时释放std::shared_ptrintp1std::make_sharedint(10);std::shared_ptrintp2p1;// p1、p2 共享计数 2// 当 p1、p2 都销毁后内存才释放动态数组的智能指针intn5;autoarrstd::make_uniqueint[](n);// 动态数组arr[0]10;// 自动释放不用 delete[]11.5 对比总结方式释放风险栈变量自动无但大小固定new/delete手动忘 delete 就泄漏unique_ptr自动无推荐shared_ptr自动引用计数对象之间互相引用时会泄漏见下现代 C 的最佳实践优先栈变量确需动态分配时用unique_ptr/shared_ptr几乎不用裸new/delete。shared_ptr唯一的坑引用循环shared_ptr的规则是计数归零才释放。如果 A 里有指向 B 的shared_ptrB 里又有指回 A 的shared_ptr两个对象的计数就永远至少是 1出了作用域也谁都不会被释放。实测g 16.1#includeiostream#includememory#includestringstructNode{std::shared_ptrNodefriend_;// 互相指回去 —— 问题就在这std::string name;explicitNode(std::string n):name(std::move(n)){}~Node(){std::coutname 被析构std::endl;}};intmain(){std::shared_ptrNodeastd::make_sharedNode(A);std::shared_ptrNodebstd::make_sharedNode(B);a-friend_b;// A 持有 Bb-friend_a;// B 持有 A形成循环std::couta.use_count a.use_count()std::endl;return0;}实测输出a.use_count 2main结束了两行A 被析构 / B 被析构一句都没打印——内存漏了。把friend_改成std::weak_ptrNode“我只观察不算计数”实测输出变成a.use_count 1 B 被析构 A 被析构规律很简单拥有的关系用shared_ptr反向指回去或我可能已经被删了的关系用weak_ptr。用weak_ptr时先lock()取出一个shared_ptr判空再用。11.6 RAIIC 资源管理的核心思想智能指针背后是一个重要思想RAIIResource Acquisition Is Initialization资源获取即初始化。意思是把资源内存、文件、锁的生命周期绑定到对象的生命周期。对象创建时获取资源对象销毁时自动释放资源。{std::unique_ptrintpstd::make_uniqueint(42);// ...使用 p...}// 出了作用域p 销毁内存自动释放正因为 C 有 RAII才不需要像 C 那样到处手动清理。Eigen 的矩阵、std::string、std::vector 全都遵循 RAII——你从来不用手动释放它们它们出作用域自动清理。11.7 小结栈自动管理堆手动管理。new/delete手动分配释放忘 delete 会内存泄漏。智能指针unique_ptr/shared_ptr自动释放是现代 C 首选。RAII资源生命周期绑定对象生命周期自动管理。下一章学习标准库容器std::vector等代替原始数组好用又安全。练习题用new分配一个 int赋值为 100打印然后delete。用new int[n]分配动态数组n 从键盘读入填充并打印最后delete[]。用std::unique_ptr分配一个 int 和一个动态数组体会自动释放。用std::shared_ptr创建两个共享同一对象的指针。用自己的话解释什么是 RAII。