腾讯WXG 客户端开发-C++ 一面
1. 自我介绍
2. 实习项目拷打
3. shared_ptr 性能优于 unique_ptr 吗,为什么
答案:一般来说不能这么说,绝大多数场景下 unique_ptr 比 shared_ptr 更轻量。unique_ptr 只有独占所有权,不需要维护引用计数,大小通常就是一个裸指针,移动也只是转移所有权。shared_ptr 需要额外的控制块来维护强引用和弱引用计数,多线程下还涉及原子操作,所以构造、拷贝、销毁成本都更高。只有在确实需要共享所有权的时候,shared_ptr 才有意义,而不是因为它“更高级”就优先使用。如果面试官继续追问,通常会往控制块、循环引用和 make_shared 上延伸。
4. make_shared 和直接 new 再构造 shared_ptr 有什么区别
答案:make_shared<T>() 一般会把对象本体和控制块放在一次内存分配里完成,分配次数更少,局部性更好,也更不容易因为中间步骤异常导致资源泄漏。而 shared_ptr<T>(new T(...)) 通常至少涉及对象本体和控制块两次分配。但 make_shared 也不是所有场景都适合,比如如果对象很大,而又有很多 weak_ptr 长时间存在,那么对象本体虽然已经析构,控制块还在,整体内存回收时机会更靠后。所以这题真正要答的是原理差异,而不是死记“哪个更好”。
代码:
#include <memory>
using namespace std;
struct A {
int x;
A(int v) : x(v) {}
};
int main() {
auto p1 = make_shared<A>(10);
shared_ptr<A> p2(new A(20));
return 0;
}
5. unique_ptr 怎么在线程池里传参,有什么问题,可以怎么解决
答案:unique_ptr 不能拷贝,只能移动,所以如果要把它交给线程池任务,通常要通过移动语义把所有权转移到任务对象里。问题主要出在两个地方,一个是很多线程池接口早期写法会假设任务参数都能拷贝,这时候 unique_ptr 直接传会编译不过;另一个是如果异步任务执行时机不确定,就要非常清楚资源所有权已经不在原线程手里,不能再继续访问。比较常见的处理方式是用 std::move 把 unique_ptr 捕获到 lambda 里,或者让线程池支持完美转发,把任务参数原样转进工作队列。如果资源本来就需要跨多个任务共享,那就不该硬用 unique_ptr,而要重新设计所有权模型。
代码:
#include <iostream>
#include <memory>
#include <thread>
using namespace std;
int main() {
unique_ptr<int> p = make_unique<int>(42);
thread t([ptr = std::move(p)]() mutable {
cout << *ptr << endl;
});
t.join();
return 0;
}
6. shared_ptr 的控制块里一般有什么
答案:控制块一般会保存强引用计数、弱引用计数、删除器、分配器,有些实现还会带类型擦除后的销毁逻辑。多个 shared_ptr 指向同一对象时,共享的是这个控制块,而不是彼此复制一份状态。当强引用计数减到 0 时,对象本体被释放;当强弱引用都减到 0 时,控制块自身才会被释放。如果答得再深入一点,还可以讲 enable_shared_from_this 为什么必须和同一控制块关联,否则会出未定义行为。
7. C++ 新特性你常用哪些,STL 用过哪些
答案:常用的新特性一般有 auto、范围 for、右值引用、移动语义、nullptr、lambda、thread、mutex、condition_variable、atomic、unique_ptr、shared_ptr、unordered_map、constexpr。STL 里常用的容器主要是 vector、deque、list、map、unordered_map、set、priority_queue,算法里常用 sort、find、lower_bound、binary_search。面试里这题最好不要报菜名式回答,通常挑几个自己真正用得多的展开,比如为什么 vector 常用、unordered_map 什么时候比 map 更合适、emplace_back 和 push_back 的差别。
8. lambda 和仿函数有什么区别
答案:lambda 本质上会被编译器生成一个匿名的闭包类型,对捕获的外部变量作为成员保存,再重载 operator()。仿函数则是开发者手写的一个类或结构体,同样通过重载 operator() 来表现得像函数。所以从本质上讲,lambda 可以看成是一种编译器帮你生成的仿函数,只不过语法更轻便,更适合局部短逻辑。如果需要复用、需要显式命名类型、需要模板参数化或者更复杂的状态管理,仿函数会更合适;如果只是回调、谓词、局部处理逻辑,lambda 更方便。
代码:
#include <iostream>
using namespace std;
struct Add {
int operator()(int a, int b) const {
return a + b;
}
};
int main() {
auto lam = [](int a, int b) { return a + b; };
Add add;
cout << lam(1, 2) << endl;
cout << add(3, 4) << endl;
return 0;
}
9. lambda 捕获 [=]、[&]、[this] 时要注意什么
答案:这题在客户端和异步代码里很常见。[=] 是值捕获,默认把用到的外部变量拷贝进闭包对象;[&] 是引用捕获,闭包里保存的是外部变量引用;[this] 则是捕获当前对象指针。真正危险的地方在于异步执行场景,比如把 lambda 投递到线程池、定时器或者 Qt 事件循环里,如果你捕获的是引用或 this,而执行时原对象已经析构,就很容易出现悬空引用。所以这题回答最好带上生命周期意识,特别是在 UI 编程和多线程场景里。
10. Qt 信号槽机制底层怎么实现
答案:Qt 的信号槽依赖元对象系统。类里加了 Q_OBJECT之后,MOC 会生成额外代码,把
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.