题目重点:Enterprise Rust/C++ 初筛只问什么是引用计数;以对象所有权、环和并发计数解释原理与失效边界。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练模型中对象有 strongCount 与 weakCount;strongCount>0 时资源存活,降到 0 时销毁资源,控制块可继续留给 weak 引用。
- 引用增减必须与所有权转移匹配;跨线程共享时需要线程安全实现,单线程计数方案不能直接跨线程用。
- 仅讨论引用计数共享所有权,不能推出自动解决所有内存安全、数据竞争或对象图循环。
解题路线
- 先给定义:每新增一个共享强所有者加 1,每放弃一个强所有者减 1;计数归零释放对象。弱引用可以观察对象但不维持寿命,升级为强引用时需原子检查对象仍存在。
- 用 A→B、B→A 的双向强边解释环:外部强引用都放弃后两个对象各仍被另一个持有,计数都不归零;把反向边设为弱引用或显式打断环。
- 跨线程复制/析构共享指针需要原子计数;这只保证生命周期计数正确,不能保证被引用对象的可变字段无数据竞争,仍需锁或不可变设计。
- 比较 RAII/所有权的确定析构与 tracing GC:引用计数通常在最后强引用释放时析构、易处理外部资源,但每次共享引用变化有计数开销且环需特别设计。
- 面试中把 Rust Rc/Arc 与 C++ shared_ptr/weak_ptr 作为具体例子,解释单线程与跨线程版本、weak 升级失败和析构副作用。
手动走一遍
训练例:初始 strong=1,复制给第二个组件后为 2;第一个释放后为 1,第二个释放后为 0,资源析构。若两个组件对象互相 strong 持有,即使外部引用均释放,两对象各剩 1;将一条边改 weak 后可依次归零。
规模与取舍
每次引用增减通常 O(1),但跨线程原子操作会引发 cache-line 争用;控制块为每对象 O(1) 额外空间。若要检测/打断一般引用环,还需遍历对象图,不能靠单个计数完成。
易错点与追问
容易失分的边界
- 把 weak 引用误认为可直接解引用且永远有效。
- 说引用计数天然回收循环强引用。
- 把原子计数等同于对象字段线程安全。
- 忽略构造/析构中的异常、回调或递归释放。
继续深挖
- Rust Rc 与 Arc 为什么不能互换?
- C++ weak_ptr.lock 为什么要检查返回值?
- 如何设计父子节点使 parent 反向引用不成环?
- 高频跨线程复制共享指针时有哪些减小争用的方法?
