尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
现代C++智能指针详解:从RAII到unique_ptr/shared_ptr/weak_ptr
你是不是也经历过这种场面代码跑着跑着突然崩了报错指向某个delete之后还在使用的指针或者内存占用一路飙升查了半天发现是某个对象被 new 出来之后丢了指向它的指针再也无法释放。早期写 C 的人十个里有七八个在内存管理上栽过跟头。裸指针raw pointer的灵活性是一把双刃剑它能让你精确控制内存生命周期也能让你在复杂项目里防不胜防。而std::unique_ptr、std::shared_ptr、std::weak_ptr这三兄弟就是我这两年用的最多的、也是现代 C 里最值得认真掌握的内存管家。这篇文章适合正在学习智能指针的 C 初学者也适合写了几年 C 但一直用裸指针裸奔的老手。我会把这三者的底层机制、适用场景、性能开销和实战中踩过的坑一并讲清楚争取让你看完之后能直接在自己的项目里用起来。1. 裸指针的顽疾与智能指针的解题思路先说清楚一个问题裸指针到底难在哪很多人觉得new 和 delete 成对出现不就行了但真实项目远没有这么简单。1.1 裸指针三大刺客泄漏、悬垂、双重释放我见过太多线上事故最后归因下来无非这三种情况内存泄漏new 出来的对象到程序结束都没被 delete。在服务端程序里这等于每秒漏一管血最终把系统内存耗尽。典型场景是把指针塞进某个容器之后忘了清理或者某个分支提前 return 导致 delete 被跳过。悬垂指针指针指向的内存已经被释放但指针变量本身还留着那个地址。下次访问它就是访问已释放内存use-after-free轻则读到脏数据重则直接 segfault。这种 bug 的可怕之处在于崩溃往往发生在完全不相干的地方排查起来极度费神。双重释放两个指针指向同一块堆内存两个地方都执行了 delete。堆管理器会崩溃给你看因为第二块被释放内存可能已经分配给别的对象了。这三种问题本质上都是所有权ownership模糊导致的。谁负责释放这块内存什么时候释放如果这段内存被多个地方引用谁说了算裸指针完全不提供这些信息只能靠程序员自己记住。但人的记忆力在几千行甚至百万行的代码库面前是靠不住的。1.2 RAII用栈对象管住堆资源智能指针的核心思想是在 1994 年左右就以 RAIIResource Acquisition Is Initialization资源获取即初始化的形态进入了 C 世界。一句话解释 RAII把资源的生命周期绑定到某个栈上对象的生命周期上。栈对象在离开作用域时会自动调用析构函数那么只要在析构函数里释放堆资源资源就永远不会漏。你平时见过的最简单的 RAII 例子就是std::lock_guard。它构造时加锁析构时自动解锁锁的保护生命周期天然覆盖了它的作用域。智能指针正是把这一套思想用在内存管理上std::unique_ptr的析构函数会 delete 它拥有的指针所以当这个栈对象走出作用域堆内存也就自动释放了。1.3 智能指针与裸指针的分工定位这里必须先立一个明确的态度智能指针不是要消灭裸指针而是要消灭拥有资源的裸指针。在代码里你依然会遇到函数之间传递临时观测用的指针位置的场景中裸指针依然可以存在只是它不再拥有所有权。现代 C 最佳实践里有个经典分层std::unique_ptr—— 独占所有权。std::shared_ptr—— 共享所有权。std::weak_ptr—— 观测者不参与所有权。裸指针 —— 非拥有型访问或者表示可空引用的临时观测。搞明白这四者的边界你就能写出既安全又有性能的现代 C 代码。2. unique_ptr独占所有权的最优解在我所有项目里std::unique_ptr是出现频率最高的智能指针。它定位清晰、零额外开销、语义响亮是我说的默认首选。2.1 为什么说它是零开销的直接拆解它的实现逻辑unique_ptr内部就是存了一个裸指针拷贝构造和拷贝赋值被 delete 掉只有移动构造和移动赋值。这意味着对象大小一般就是裸指针的大小8 字节64 位系统下与T*完全一致。解引用操作*ptr和-是直接转发给内部裸指针的没有额外间接层。析构时直接delete ptr没有引用计数增减没有原子操作。我做过一个粗略的压测在 Release 开启优化后unique_ptr的解引用和裸指针的汇编指令几乎一模一样。所以它真的不牺牲性能。2.2 使用场景与方法unique_ptr最常见的三个使用场景代替 new/delete 管理类成员。以前你在类里声明T* m_ptr构造函数 new析构函数 delete还要处理拷贝构造函数、拷贝赋值要么深拷贝要么禁止复制。用了unique_ptr之后这些全都不用手写了。因为unique_ptr本身不可拷贝你的类默认就禁止了拷贝移动如果需要的话也能自动转交给unique_ptr的移动语义。工厂函数返回独占对象。这是我很推荐的写法。比如std::unique_ptrDatabaseConnection createConnection(const DbConfig config) { auto conn std::make_uniqueDatabaseConnection(config.address); conn-connect(); return conn; }这里return conn会触发移动语义甚至直接 RVO调用方能明确知道连接的所有权在自己手上。放进容器里。像std::vectorstd::unique_ptrTask容器销毁时里面的任务对象会自动释放。相比vectorTask*你不用再手写循环去 delete 每个元素exception safety 也天然得到了保障。2.3 它不好用的地方共享需求一出现就尴尬unique_ptr最大的限制就是不能拷贝。如果你有两个模块需要同时操作同一个对象而且生命周期由双方共同决定那就不能靠unique_ptr了。有些新手会试图用std::move(ptr)把所有权交给别人结果原调用方后续再访问ptr发现是空指针。这不是 bug而是所有权转移的预期行为——但如果你没预料到这种转移代码就会变得混乱。所以unique_ptr合适的是一条清晰的所有权主线不合适的是多方共享、生命周期交叉的场景。3. shared_ptr共享所有权的灵活性与代价如果说unique_ptr是一款轻便跑车shared_ptr就是一辆加满安全气囊的重型皮卡。它的核心卖点非常直接它能让多个智能指针安全地同指一块内存并且当最后一个指针被销毁时自动释放内存。3.1 控制块的内部构造要理解shared_ptr必须看看它内部长什么样。每一个shared_ptr实例内部有两样东西指向对象本身的裸指针以及一个指向控制块control block的裸指针。控制块里通常包含引用计数ref count——统计当前有多少个shared_ptr指向该对象。弱引用计数weak count——统计有多少个weak_ptr正在观测该对象。删除器deleter——实际执行释放动作的函数。有时还有allocator信息。关键点在于引用计数是原子操作。拷贝一个shared_ptr时ref count 执行原子加一析构时执行原子减一。因此多线程环境下复制和销毁shared_ptr是线程安全的——注意这只是指计数操作线程安全不代表指向的对象本身线程安全。这个区别我后面会专门展开。3.2 make_shared 的两点优势我几乎永远用std::make_sharedT(args...)而不是std::shared_ptrT(new T(args...))。原因有二第一效率更高。make_shared会一次性分配一块足够大的内存同时容纳对象本身和控制块。而分开写的方式需要两次堆分配一次给对象一次给控制块。在频繁创建销毁的场景make_shared的分块内存在局部性上也更好。第二异常安全。考虑这段代码process(std::shared_ptrObj(new Obj), getSomething());C 里的求值顺序在早期标准里是不确定的。编译器的执行顺序完全可能是先new Obj接着调用getSomething()结果getSomething()抛异常那么 new 出来的 Obj 就没有被包裹到shared_ptr里内存泄漏了。而make_shared把这个过程封装成一个原子调用异常发生时也不会泄漏。3.3 一个隐藏的坑make_shared 导致的内存无法立刻归还事物都有两面性。make_shared因为把对象和控制块放在同一块内存里释放流程就变成当最后一个shared_ptr被销毁时对象本身被析构并销毁但那整块内存并不会立刻归还给系统必须等到最后一个weak_ptr也消失。这是因为控制块要活到最后一个弱引用结束。如果对象那部分内存非常大比如一个 100MB 的缓冲区又有一些weak_ptr长时间挂着不释放那么这 100MB 会一直占着内存看起来像泄漏。遇到这种超大对象 长时间存活的 weak_ptr组合建议改用分开的new shared_ptrT(new T(...))方式让对象内存可以及时释放控制块虽然多一次分配但保持独立生命周期。4. weak_ptr不增加所有权的观测者weak_ptr是最容易被忽视但恰恰是解决循环引用这个老大难的关键工具。4.1 循环引用到底是怎么发生的两个对象互相持有对方的shared_ptr就会形成引用循环。看我手头一个简化过的例子struct Node { std::shared_ptrNode next; std::shared_ptrNode prev; }; Node* a new Node(); Node* b new Node(); a-next b; b-prev a;这种情况下a和b的引用计数始终至少为 1谁也不会释放。一对一的循环还算好发现麻烦的是链表、图、观察者模式里那种三五成群的互相引用靠肉眼排查非常头疼。4.2 weak_ptr 如何打破循环weak_ptr的设计哲学就是只观察不拥有。它可以由shared_ptr构造也可以互相构造但它不会增加对象的引用计数。它内部只维护弱引用计数weak count这个不计入对象是否存活的判断。你需要用它访问对象时调用lock()方法std::weak_ptrNode wptr a-next; // 观测不增加引用计数 if (auto shared wptr.lock()) { // 这个 if 里对象还活着shared 持有所有权 std::cout shared-value std::endl; } else { // 对象已被释放安全处理 }lock()返回一个新的shared_ptr里面是原子地尝试递增引用计数如果对象已释放就返回空指针。可以理解为临时借一把伞确认没被雨淋到才撑开。把前面循环引用的例子改成prev用weak_ptrb对a的依赖就只是观测a释放时b的prev会自动悬空lock()会返回空你也不会去访问已释放内存。4.3 缓存、观察者与分层数据中的典型用法除了打破环weak_ptr最常见的用途还有实现对象缓存一个全局缓存容器不持有对象所有权只存weak_ptr业务代码需要时去lock()拿临时使用权对象用完即释放缓存不会拖累内存。观察者模式主题Subject向观察者Observer发送通知时它不应该拥有观察者的生命周期。持有weak_ptrObserver通知前lock()一下如果观察者已销毁就自动跳过。父-子节点设计子节点持有父节点的weak_ptr避免父子互相拥有。用一句话记当你需要引用别人但不应承担别人生命周期责任时该用 weak_ptr。5. 自定义删除器与数组管理的正确姿势很多教程讲完三种智能指针的基本用法就结束了但实际项目里马上会遇到两个进阶问题对象不是用new分配出来的怎么办指向数组怎么办5.1 删除器定制不止是 delete 的替代品unique_ptrT, D和shared_ptrT都支持自定义删除器。常见需求是资源来自非标准分配方式比如// 文件句柄fopen 对应的不是 delete而是 fclose auto deleter [](FILE* fp) { if (fp) fclose(fp); }; std::unique_ptrFILE, decltype(deleter) filePtr(std::fopen(data.txt, r), deleter);类似的还有 OpenGL 纹理句柄、Windows HANDLE、第三方 C 库创建的指针等都能用智能指针包裹把释放逻辑集中收口。要注意unique_ptr的自定义删除器是类型的一部分所以不同 lambda 是不同的类型声明函数签名时需要写清楚而shared_ptr的删除器不参与类型它的类型始终是shared_ptrT删除器在运行时被保存在控制块里。5.2 数组正确姿势其实是 vector先说结论能用 std::vector 就不要用智能指针管理数组。vector已经完美解决了动态数组的分配、扩容、释放问题还有丰富的迭代器接口。但如果你确实在维护老接口有人用new int[10]返回了裸指针那你需要std::unique_ptrint[]这种带[]的数组特化版本。它析构时会调用delete[]而不是delete并且提供operator[]来按索引访问元素std::unique_ptrint[] arr(new int[10]); arr[0] 42;而shared_ptrint[]则需要自己传一个自定义删除器std::default_deleteint[]否则默认会调用delete行为未定义。这也是新手最容易写错的地方。5.3 enable_shared_from_this 的使用时机还有一种实战里经常遇到的问题某个成员函数需要一个指向自身的shared_ptr。如果你直接std::shared_ptrFoo(this)就会创建出第二个控制块最终导致双重释放这是经典的未定义行为。正确做法是让类继承std::enable_shared_from_thisFoo然后在成员函数里调用shared_from_this()struct Foo : std::enable_shared_from_thisFoo { std::shared_ptrFoo getHandle() { return shared_from_this(); } };这个内部逻辑是enable_shared_from_this里额外存了一个weak_ptr记录当前对象所属的控制块信息。注意一个前提对象必须已经由 shared_ptr 管理shared_from_this 才能正常工作如果一个栈上的 Foo 对象调用了它会抛出std::bad_weak_ptr。我在回调机制比如异步任务里this可能已销毁的场合中用得很多。它本质上就是安全地把自己托付给 shared_ptr 网络的桥梁。6. 性能开销白皮书该省的地方省该花的地方花既然标题叫详解性能这块不能不说。很多团队对智能指针的性能有偏见觉得智能指针对性能影响大。这个判断要分层次来看。6.1 三种指针的开销对比操作unique_ptrshared_ptrweak_ptr构造/析构与裸指针相当原子计数操作 控制块操作与 shared_ptr 类似但更轻拷贝禁止需移动原子加一原子加一解引用/访问零额外副作用内存间接层极轻微需 lock()有额外原子操作内存占用与裸指针相同多一个控制块指针多一个控制块指针线程安全需要自行保证对象访问安全计数安全对象访问仍需同步lock() 本身线程安全最核心的结论是unique_ptr几乎没有额外开销该省的地方要比shared_ptr省得多。shared_ptr的开销集中在两点原子引用计数、控制块的堆分配。单个操作快则几十纳秒但是每秒百万次级别的创建/销毁累计下来就很可观。如果你的路径上要高频创建和销毁管理对象优先考虑unique_ptr或对象池。weak_ptr的开销不比shared_ptr小多少因为lock()要做原子操作和状态检查。所以它应该只是正确的设计需要时才用而不是作为普遍的传参类型。6.2 传参时如何避免无谓的拷贝这属于老生常谈但值得再强调一次的关键点。很多人在函数接口设计中乱用智能指针传参导致无谓的原子操作。我的推荐路径如果函数只需要使用对象的成员不参与生命周期管理传const T或裸指针T*。调用方自行保证对象在调用期间存活。如果函数需要持有对象所有权传值std::unique_ptrT表示移动所有权进来或者std::shared_ptrT将引用计数加一。如果函数需要复用共同所有权传const std::shared_ptrT内部需要时才拷贝一份但这个方法会造成函数调用时仍有一次计数的增加与减少性能不理想。如果函数只观测对象传const std::weak_ptrT或std::shared_ptrT const后调用lock()。有个经典的面试题shared_ptr作为函数参数该用值、常引用还是裸指针其实没有绝对答案核心是看你在函数内部是否有共享所有权的需求。没有这个需求就别传shared_ptr。6.3 对象很小、数量很大时的特别提醒如果你管理的是大量尺寸很小的对象比如几字节的结构体shared_ptr的控制块开销会比对象本身还大内存消耗直接翻倍甚至更多。这种情况下一个经验是考虑用unique_ptr配合值语义的容器或者更极端一点如果生命期整齐划一根本别用智能指针直接用对象池或vectorT管理大批对象只在引用处使用裸指针或索引。见过太多团队无脑make_shared内存占用直接爆表。7. 实战踩坑录从崩溃到修好的完整排查链路智能指针的报错相比裸指针段错误而言已经友好很多但也绝不意味着没有坑。我把自己在真实项目里反复踩过的几个问题完整梳理一遍这些经验你直接可以抄作业。7.1 问题场景一shared_ptr 藏在回调里对象却提前销毁了某服务里我在异步回调里捕获了this执行回调时发现this已失效直接借用了shared_from_this()来保证生命周期覆盖整个异步任务。可是运行时崩在了回调执行地排查发现shared_from_this()抛了bad_weak_ptr。排查链路先加日志确认enable_shared_from_this是否被正确继承 → 再断点看对象创建方式 → 最后发现有个入口用的是裸指针new Foo塞进接口没有包裹成shared_ptr。shared_from_this()内部那个weak_ptr没有初始化自然抛异常。教训凡是可能进入共享所有权体系的对象创建路径必须统一经make_shared或shared_ptr构造不能混用裸指针初始化。7.2 问题场景二shared_ptr 数据竞争引用计数安全救不了对象访问多线程任务池并行处理同一个shared_ptrImage多个线程同时访问图像像素结果图像数据错乱。有人质问我shared_ptr 不是线程安全的吗排查链路查计数是好的查像素数据访问竞态 → 用 TSanThreadSanitizer跑一遍直接定位到两个线程对同一像素缓冲区无锁并发读写。教训shared_ptr的线程安全只覆盖控制块的计数与状态对指向的实际对象没有任何保护。多个线程通过同一个shared_ptr读写对象还是要自己加锁或使用原子操作。这个理解太重要了因为面试和实际代码审查经常问。7.3 问题场景三自定义删除器被忽略接手某遗留 C 工程有个对象内部用了mmap映射的共享内存旧代码用裸指针到处传递。我用shared_ptr包裹后没传自定义删除器默认走了delete。崩溃报了 munmap invalid检查发现释放路径错了才改了删除器auto shmDeleter [](void* addr) { if (addr ! MAP_FAILED) munmap(addr, size); }; std::shared_ptrvoid shmPtr(mmap(...), shmDeleter);教训接管任何非 new 分配资源前一定要确认释放 API。不熟悉的第三方库先看它 release/free/fclose 接口在哪儿。7.4 问题场景四循环引用不从 Node 出发而是从回调闭包出发有一个消息总线客户端对象订阅总线时把shared_ptrClient存进总线的回调列表里同时客户端内部保存shared_ptrBus导致总线↔客户端互相持有服务退出时内存不下降。排查链路用系统内存监控看到对象数量不减少 → 用 Valgrind 定位到未释放对象 → 用对象图的思路梳理谁引用了谁 → 发现循环。修复总线侧改存weak_ptrClient回调时lock()获取客户端如果客户端已销毁就自动移除回调。这样总线不拥有客户端循环断了。通用解法思路画对象引用图找出环然后在环的某一条边改用weak_ptr。优先的方向是生命周期较长的服务端不拥有生命周期较短的客户端。7.5 调试工具速查遇到智能指针相关的内存问题我的检查顺序一般是打开-fsanitizeaddress重新编译很多 use-after-free 和泄漏会直接定位到代码行。打开-fsanitizethread检查并发访问。跑 Valgrind 做泄漏检测锁定未释放对象的大致来源。如果确定是 shared_ptr 循环引用写一个小单元测试采样观察对象析构函数是否被调用在析构里打日志最直观。这套组合拳基本能覆盖 99% 的智能指针问题场景。8. 从 C11 到 C17/20智能指针的演进与后续展望我第一次接触智能指针时社区还在争论auto_ptr有多坑。现在的现代 C 已经演进得很成熟一些 API 也在持续变化值得简单梳理一下。8.1 auto_ptr被移动语义淘汰的旧时代产物auto_ptr是 C98 时代的临时方案它的拷贝操作实际上是转移所有权。写auto_ptrint p1 p2之后p2变成空指针这违反直觉且非常容易隐蔽地把数据悄悄掏空。C11 引入真正的移动语义后auto_ptr就被彻底弃用了。现在unique_ptr通过std::move显式转移所有权比auto_ptr的隐式搬移清晰得多。如果你在维护老代码看到auto_ptr请及时替换为unique_ptr。替换时注意差别unique_ptr只能显式移动调用std::move的地方得补上。8.2 make_unique 在 C14 正式补票std::make_shared从 C11 就有了但std::make_unique直到 C14 才进标准库。我见过不少团队在 C11 模式下自己实现一个make_unique工具函数代码很简单但容易写错。如果你还在用 C11我的建议是查一下自己的编译器和标准库版本尽量升级到 C14 以上反正make_unique是你迟早要用的必需品。8.3 C17/20 的影响C17 强化了复制省略copy elision返回局部unique_ptr/shared_ptr更高效基本不用担心多一次移动的代价。C20 引入了std::make_shared_for_overwrite/std::make_unique_for_overwrite它们允许创建未初始化的对象数组适合那些你马上会用memcpy或循环覆盖内容的场景可以省掉不必要的初始化开销。概念concepts和协程coroutines的引入进一步让异步编程更自然但智能指针在异步代码中的地位只会更重因为异步回调往往最需要安全的所有权转发。智能指针本身的设计已经很稳定未来大概率不会出现颠覆性变化。真正值得持续关注的是如何在大型项目里建立统一规范让所有人按所有权模型来编程。9. 最后分享一个我一直在用的团队规范我负责过的几个项目代码评审时对指针用法的要求基本固定成了一套规则简称为三不一条不写裸new/delete除非是极底层库的明确要求。不直接拷贝shared_ptr要么make_shared要么const传入。不在接口里用weak_ptr作为直接参数先lock()转成shared_ptr再进函数逻辑。一条能用unique_ptr就不要用shared_ptr能用weak_ptr观测就不要用shared_ptr拥有。这套规则帮我挡住了很多隐蔽 bug也让新人接手代码时心里有底。因为所有权一旦清晰内存管理就变成了自动档你只需要在换挡转移所有权、共享所有权时稍微留意。智能指针不是银弹但把这套工具用好C 写起来会舒心很多。下次有人再问智能指针什么时候用哪个你可以直接把这篇文章甩给他然后加一句先画所有权图再选类型别凭感觉。
RELATED

相关推荐

Open-Lovable宝塔部署实战:AI网站生成器自托管全攻略

Open-Lovable宝塔部署实战:AI网站生成器自托管全攻略

最近圈子里又聊起一个有意思的开源项目——Open-Lovable。说白了,这就是一个 AI 网站克隆工具,目标是让你在自己服务器上复刻一套类似 Lovable 那样"输入一句话生成前端 后端应用"的能力。很多人第一反应是:这东西不是直接在线用就…

📅 2026/10/14 8:05:17
多智能体协作架构实战:从单点工具到AI虚拟团队的工作台搭建指南

多智能体协作架构实战:从单点工具到AI虚拟团队的工作台搭建指南

1. 从单点工具到协作团队:为什么业务需要多智能体架构过去两年,我接触过不少团队在业务里接入大模型,绝大多数都停留在“单点工具”阶段——写文案的用一个对话框,做数据分析的用另一个,客服问答再单独搭一套。这种模式…

📅 2026/10/14 8:05:17
数据库期末复习:SQL书写、范式分解与事务并发控制试题解析

数据库期末复习:SQL书写、范式分解与事务并发控制试题解析

简介:数据库期末考试试题及答案是一套面向高校学生的数据库课程复习资料,系统覆盖数据库系统核心概念、数据模型、实体联系模型、关系规范化、事务处理、数据库恢复等高频考点。资源包共1个doc文档,大小118KB,内含完整试卷与参考答…

📅 2026/10/14 8:00:17
MORE NEWS

更多资讯

📰

智能体记忆系统横评与治理

智能体记忆系统横评与治理 生成式人工智能正从单轮问答迈向具备长期交互能力的自主智能体。在企业级落地中,跨会话的偏好沉淀、上下文召回与数据治理,直接决定 Agent 的任务完成率与合规边界。 本文聚焦以下四个核心问题: 主流智能体记忆系…

📰

Hermes agent 贡献指南:从环境搭建到 PR 合并的完整实操

1. 从零理解 Hermes agent 的贡献体系1.1 这个项目到底在解决什么问题第一次接触 Hermes agent 的人,大概率会经历一个相同的困惑:文档看了一圈,知道它能做任务编排、能接工具、能跑多轮对话,但真到了想给它提个 PR 或者补个功能的…

📰

WPF中ObservableCollection排序的正确姿势:ICollectionView实战指南

1. 项目概述:为什么WPF里对ObservableCollection排序会让人抓狂? 在WPF开发中, ObservableCollection 是绝大多数人绑定列表数据的默认选择——它自带通知机制,UI能自动响应增删改。但一旦你尝试给它加个“按姓名升序”或“按时间…

📰

SAR点目标指标计算:sinc函数到分辨率、PSLR与ISLR的工程实践

简介:面向SAR成像、雷达成像与遥感信号处理学习者的一份MATLAB配套资料包,聚焦sinc函数这一理想点扩散函数,系统梳理分辨率、峰值旁瓣比(PSLR)、积分旁瓣比(ISLR)三个核心点目标聚焦指标。压缩包…

📰

课堂行为识别系统实战拆解:YOLO检测、行为分类与评价规则

简介:一份基于深度学习的学生课堂行为识别评价综合系统源码,属于导师指导并通过评审的毕业设计项目,评审得分98分,适合计算机相关专业正在准备毕业设计的学生,也适合需要完整项目实战练习的深度学习入门与进阶学习者。…

📰

【AI大模型进阶】微调中的过拟合表现:训练集 loss 降了,测试集降了吗?

【AI大模型进阶】微调中的过拟合表现:训练集 loss 降了,测试集降了吗? 集 loss 降了,测试集降了吗? 写在前面 大模型微调不管是全参数微调、LoRA 微调、QLoRA 低秩微调,大家最直观的监控指标就是损失 loss。很多人训练的时候看到训练 loss 一路持续下降,就默认模型效…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬