智能指针unique_ptr与weak_ptr:面试官问“三种智能指针怎么选“,我差点只说出shared_ptr

智能指针unique_ptr与weak_ptr:面试官问“三种智能指针怎么选“,我差点只说出shared_ptr 上篇聊了shared_ptr很多人觉得智能指针就等于shared_ptr。面试被问到你了解哪些智能指针有人只答了shared_ptr然后面试官笑着说还有呢——那个场面比代码编译失败还尴尬。C11引入了三种智能指针。shared_ptr上篇讲过了这篇把剩下两位请出来。讲真在实际项目里unique_ptr用得比shared_ptr还多。weak_ptr虽然用得少但用到的时候往往是解决关键问题的。unique_ptr最朴素的 Ownershipunique_ptr的设计哲学很简单独占所有权。一个资源同一时间只能有一个unique_ptr拥有它。unique_ptrLidarDriver lidar(new LidarDriver(9090)); lidar-scan(); // 函数结束lidar自动销毁不需要手动delete跟shared_ptr比unique_ptr最大的优势是什么零开销。shared_ptr要维护引用计数每次拷贝都要原子操作。而unique_ptr不维护任何计数它的大小就跟一个裸指针一样。在性能敏感的代码里这个差距是实打实的。unique_ptrLidarDriver p1(new LidarDriver(9090)); // unique_ptrLidarDriver p2 p1; // 编译错误不能拷贝 unique_ptrLidarDriver p3 std::move(p1); // 可以移动所有权转移 // 此时p1已经为空p3拥有对象unique_ptr不能拷贝只能移动。这个设计就是为了防止两个unique_ptr同时指向同一个对象。移动语义下篇会详细讲。机器人开发中unique_ptr的典型用法在机器人系统里硬件设备的管理天然适合unique_ptr。想想看一台机器人上挂着激光雷达、IMU、深度相机。每个设备只有一个主人在管它。你总不能说这个激光雷达同时属于导航模块和避障模块——那不就乱套了class RobotHardware { private: unique_ptrLidarDriver lidar_; unique_ptrIMUDriver imu_; unique_ptrCameraDriver camera_; public: RobotHardware() : lidar_(make_uniqueLidarDriver(9090)), imu_(make_uniqueIMUDriver(/dev/ttyUSB0)), camera_(make_uniqueCameraDriver(0)) {} };make_unique是C14引入的便利函数比直接new更安全。还有一个场景工厂函数返回对象。创建不同传感器驱动的工厂返回unique_ptr就很合适——调用者拿到了所有权工厂不再持有。unique_ptrSensorBase createSensor(const string type) { if (type lidar) return make_uniqueLidarDriver(9090); if (type imu) return make_uniqueIMUDriver(/dev/ttyUSB0); return nullptr; }weak_ptr解决shared_ptr的暗病shared_ptr有一个经典问题循环引用。两个对象互相持有shared_ptr引用计数永远降不到0内存就泄漏了。这个bug很隐蔽表面看代码逻辑没问题但跑着跑着内存就涨上去了。class Node; class Edge { public: shared_ptrNode from, to; }; class Node { public: vectorshared_ptrEdge edges; }; // node1→edge→node2→edge循环引用谁都释放不了weak_ptr就是来解决这个问题的。它指向shared_ptr管理的对象但不增加引用计数。class Edge { public: weak_ptrNode from; // 改为weak_ptr不增加计数 weak_ptrNode to; }; // 现在引用链可以正常断开对象能正确销毁使用weak_ptr要注意它指向的对象可能已经被销毁了。访问之前必须调用lock()把weak_ptr提升为shared_ptrvoid processNode(weak_ptrNode wp) { if (auto sp wp.lock()) { sp-doSomething(); // 对象还活着安全使用 } else { cout Node already destroyed endl; } }这个lock()的使用模式面试里经常考。有些候选人知道用weak_ptr但忘了lock就直接访问运行时直接崩。三种智能指针的选型策略面试官问三种智能指针怎么选我的经验是这样的默认用unique_ptr。零开销语义最清晰。大部分场景下这就是你需要的。需要共享所有权的时候用shared_ptr。比如多个模块共同访问同一个数据对象而且你确定没有循环引用。典型场景缓存、配置对象、全局资源管理器。需要在引用链中打断循环的时候用weak_ptr。或者想观察一个对象但不影响它的生命周期——比如回调函数里引用一个可能已经被销毁的对象。在机器人开发里我的习惯是硬件设备管理用unique_ptr共享的配置参数用shared_ptr回调函数里的对象引用用weak_ptr。class NavigationModule { private: unique_ptrPathPlanner planner_; // 独占我创建我管理 shared_ptrMapData map_; // 共享多模块用同一份地图 weak_ptrSensorFusion fusion_; // 观察fusion可能先被销毁 };面试中的常见追问unique_ptr能不能放到容器里可以。vectorunique_ptrBase是多态容器的标准写法。用make_unique直接emplace就行vectorunique_ptrSensorBase sensors; sensors.push_back(make_uniqueLidarDriver(9090));shared_ptr能不能指向栈上的对象不能。shared_ptr只能管理堆上的对象。让它指向栈对象析构的时候会delete一个栈地址直接崩溃。再聊一个实战中很有用的技巧怎么用调试器观察shared_ptr的引用计数。在GDB里你可以用print sp.use_count()来查看某个shared_ptr当前的引用计数值。在排查循环引用导致的内存泄漏时这个方法特别管用——如果你发现两个shared_ptr互相指向对方引用计数始终大于1降不下来那就确认是循环引用了。解决方法就是把其中一方改成weak_ptr。另外在代码review时我通常会重点关注shared_ptr的拷贝——每次拷贝都意味着引用计数加一如果在不必要的地方频繁拷贝shared_ptr不仅浪费性能还可能导致生命周期超出预期。面试时如果你能提到用GDB调试引用计数的经验面试官会觉得你确实动手排查过智能指针相关的问题。给正在准备面试的你三种智能指针的选型说白了就是所有权的设计。unique_ptr是我的shared_ptr是我们的weak_ptr是我看看但不参与。面试的时候能把三种指针讲清楚再举出实际项目中的选型理由面试官会觉得你对资源管理有真正的理解。不是背概念而是知道什么时候用什么。下篇聊Lambda表达式——ROS2回调函数里的标配写法。这个特性在现代C里太常用了面试几乎必考。如果这篇文章对你有帮助欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。「机器人软件开发面试·从入门到精通」连载系列上一篇第35篇 智能指针shared_ptr——现代C内存管理的答案 下一篇预告第37篇 Lambda表达式——ROS2回调函数中的标配写法有任何问题欢迎评论区留言我会尽量回复。