小米汽车 中间件-C++ 一面
1. 自我介绍
2. 你在恒生电子实习学了点什么
3. 智能指针,开发中用过吗
用过,主要接触比较多的是 unique_ptr、shared_ptr 和 weak_ptr。在实际开发里,unique_ptr 我一般会用在所有权非常明确的场景,比如某个对象独占一个资源;shared_ptr 更多用于多个模块共享对象生命周期的情况;weak_ptr 则主要用于打破循环引用或者做非拥有式观察。我现在对智能指针的理解不只是“替代裸指针”,更重要的是它把资源归属和生命周期表达得更明确,能减少手动释放资源带来的风险。不过智能指针也不是用了就一定安全,像循环引用、重复托管同一块内存、跨线程访问对象状态这些问题还是要单独注意。
代码:
#include <iostream>
#include <memory>
using namespace std;
class Node {
public:
Node() { cout << "construct\n"; }
~Node() { cout << "destruct\n"; }
};
int main() {
unique_ptr<Node> p1 = make_unique<Node>();
shared_ptr<Node> p2 = make_shared<Node>();
weak_ptr<Node> wp = p2;
if (auto sp = wp.lock()) {
cout << "object alive\n";
}
return 0;
}
4. unique_ptr 和 shared_ptr 指向同一块内存会有什么问题
如果 unique_ptr 和 shared_ptr 指向同一块通过裸指针分配出来的内存,而且它们彼此不知道对方的存在,那么本质上就是两个独立的所有权管理者在管理同一份资源。这样会导致重复释放。因为 unique_ptr 析构时会删一次,shared_ptr 的引用计数归零时也会再删一次,最终出现 double free,行为未定义。这类问题的根源不是“指针类型不同”,而是同一块内存不能被多个独立所有权体系重复接管。如果对象一开始就准备用共享所有权,那就应该直接用 make_shared 或者明确由 shared_ptr 接管;如果是独占所有权,就只保留 unique_ptr。
代码:
#include <memory>
using namespace std;
int main() {
int* p = new int(10);
unique_ptr<int> up(p);
shared_ptr<int> sp(p); // 错误:同一裸指针被两个所有权体系接管
return 0;
}
5. 同一个裸指针构造两个不同的 shared_ptr 会怎么样,为什么
如果用同一个裸指针分别构造两个不同的 shared_ptr,这两个 shared_ptr 会各自生成独立的控制块,它们的引用计数互不关联。这样表面上看两个 shared_ptr 都在“共享”这块内存,但实际上它们共享的不是同一个控制块。最后两个 shared_ptr 生命周期结束时,都会各自认为自己应该释放对象,于是发生重复释放。正确做法是:一个裸指针只能交给一个 shared_ptr 体系管理,之后其他地方如果也要共享,应该通过拷贝已有的 shared_ptr 来共享控制块,而不是再次从裸指针构造。
代码:
#include <memory>
using namespace std;
int main() {
int* p = new int(42);
shared_ptr<int> sp1(p);
shared_ptr<int> sp2(p); // 错误:两个独立控制块
return 0;
}
6. shared_ptr 的控制块里通常有什么,如何实现
shared_ptr 底层通常不只是一个裸指针,它还关联一个控制块。控制块里一般会保存几类信息:对象指针或者对象本体、强引用计数、弱引用计数、删除器、分配器等。当强引用计数减到 0 时,对象析构;当强引用和弱引用都为 0 时,控制块本身才释放。从实现角度看,关键是要把“对象生命周期”和“控制块生命周期”区分开。对象可以先销毁,但只要还有 weak_ptr 在观察,控制块就不能立刻没掉。这也是为什么 weak_ptr 能判断对象是否存活,因为它依赖的其实是那份还活着的控制块。
代码:
#include <atomic>
using namespace std;
template <typename T>
struct ControlBlock {
T* ptr;
atomic<int> shared_cnt;
atomic<int> weak_cnt;
ControlBlock(T* p) : ptr(p), shared_cnt(1), weak_cnt(0) {}
};
7. static 的作用
static 在 C++ 里有几种常见含义。修饰局部变量时,表示这个变量存储期是整个程序运行期,但作用域仍然在函数内部;修饰类成员变量时,表示它属于类本身而不是某个对象;修饰函数或者全局变量时,在当前编译单元内可见,相当于限制链接范围。如果从面试角度讲,我一般会把它总结成两类作用:延长生命周期和控制可见性/归属关系。像单例里常见的局部静态对象,就是把生命周期和线程安全初始化一起利用起来。
代码:
#include <iostream>
using namespace std;
void func() {
static int cnt = 0;
++cnt;
cout << cnt << endl;
}
int main() {
func();
func();
return 0;
}
8. RAII 思想还有哪些应用
RAII 的核心是把资源获取和对象生命周期绑定起来,对象构造时拿资源,对象析构时释放资源。它的应用远不只是智能指针。比如 lock_guard 和 unique_lock管理互斥锁,文件流对象管理文件句柄,数据库连接包装类管理连接,线程封装类管理线程 join/detach,甚至事务回滚保护器本质上也可以看成 RAII。它最大的价值在于异常安全。因为只要对象能正常析构,资源就能自动回收,不容易因为中途 return 或异常抛出导致资源泄漏
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.
