CVTE C++ 二面总结
项目与架构
1. 介绍一下你做过的最复杂的项目,重点说说技术架构和你的贡献
回答思路:
- 项目背景:业务场景、用户规模、技术挑战
- 架构设计:分层架构、技术选型、为什么这样设计
- 个人贡献:负责哪个模块、解决了什么核心问题
- 技术亮点:性能优化、高可用方案、创新点
- 结果量化:性能提升多少、支撑多大规模
示例框架:"我做过一个分布式文件存储系统,支持百万级文件上传下载。架构上分为三层:接入层用Nginx做负载均衡,业务层是C++写的存储服务集群,数据层用MySQL记录元数据、实际文件存在分布式文件系统。我主要负责存储服务的开发,实现了文件分块、断点续传、秒传等功能。技术难点是如何保证高并发下的数据一致性,我们用了分布式锁和两阶段提交。最终系统支持单机5000 QPS,文件上传成功率99.9%。"
2. 如果让你设计一个高性能的网络库,你会怎么做
回答要点:
IO模型选择:
- Linux下用epoll,因为没有fd数量限制,而且不需要遍历所有fd
- 选择边缘触发(ET)模式,性能更高,但要注意必须循环读写直到EAGAIN
- 配合非阻塞IO使用
线程模型:
- 采用Reactor模式,多Reactor多线程
- 主线程负责accept新连接,然后分发给IO线程
- 每个IO线程一个epoll实例,处理多个连接的读写
- 业务逻辑可以放到独立的工作线程池处理,避免阻塞IO线程
关键设计:
- 连接管理:用map存储fd到连接对象的映射,连接对象包含读写缓冲区
- 定时器:用时间轮或最小堆管理超时连接,定期清理
- 缓冲区:读缓冲区和写缓冲区分离,支持自动扩容
- 对象池:预分配连接对象,减少new/delete开销
性能优化:
- 零拷贝:用sendfile或splice减少数据拷贝
- 批量处理:一次epoll_wait获取多个事件,批量处理
- CPU亲和性:把IO线程绑定到特定CPU核心,提高cache命中率
参考:muduo、libevent这些成熟框架的设计思路
C++核心特性
3. 说说智能指针的原理,shared_ptr是线程安全的吗
回答要点:
unique_ptr:
- 独占所有权,不能拷贝只能移动
- 底层就是封装了一个裸指针,析构时delete
- 零开销,和裸指针性能一样
- 适合明确所有权的场景
shared_ptr:
- 共享所有权,内部维护引用计数
- 有两个指针:一个指向对象,一个指向控制块(包含引用计数)
- 引用计数用原子操作实现,保证线程安全
- 拷贝时计数加1,析构时计数减1,为0时释放对象
线程安全性(重点):
- 引用计数的增减是线程安全的,因为用了atomic
- 但是对象本身的读写不是线程安全的,多线程访问需要加锁
- 指针本身的修改也不是线程安全的,比如多线程同时给同一个shared_ptr赋值
举例说明:"假设有个shared_ptr<int> p指向42,多个线程同时拷贝p是安全的,因为引用计数是原子操作。但如果多个线程同时修改*p的值,就会有数据竞争。如果多个线程同时给p赋新值,也会有问题,需要外部加锁。"
weak_ptr:
- 不增加引用计数,用来打破循环引用
- 使用前要lock()转成shared_ptr,检查对象是否还存在
4. 讲讲右值引用和移动语义,为什么需要它们
回答要点:
为什么需要移动语义:
- 传统C++拷贝开销大,比如vector拷贝要深拷贝所有元素
- 很多时候我们不需要保留原对象,比如函数返回临时对象
- 移动语义可以"窃取"资源,避免深拷贝
右值引用:
- 用&&表示,绑定到临时对象(右值)
- 延长临时对象的生命周期
- 让我们能区分"可以被移动的对象"
移动构造和移动赋值:
- 移动构造:窃取资源,把原对象的指针拿过来,原对象置空
- 移动赋值:类似,但要先释放自己的资源
- 要标记为noexcept,因为容器需要这个保证
std::move的作用:
- 本质是强制类型转换,把左值转成右值引用
- 告诉编译器"这个对象可以被移动"
- 注意move之后原对象处于有效但未定义状态,不要再使用
实际应用:
- 容器插入:push_back(std::move(obj))避免拷贝
- 返回局部对象:编译器会自动优化(RVO)
- unique_ptr只能移动不能拷贝,体现独占所有权
完美转发:
- std::forward保持参数的左右值属性
- 用于模板函数,把参数原样转发给其他函数
- 实现通用的包装器
5. 虚函数的实现机制是怎样的,有什么性能影响
回答要点:
虚函数表(vtable):
- 每个有虚函数的类有一个虚函数表,存储虚函数的地址
- 对象内存布局:最前面是虚表指针(vptr),然后是成员变量
- vptr在构造函数中初始化,指向对应类的vtable
虚函数调用过程:
- 通过对象的vptr找到vtable
- 根据函数在vtable中的索引找到函数地址
- 间接调用该函数
- 这是运行时多态的基础
多继承的情况:
- 如果多个基类都有虚函数,派生类对象会有多个vptr
- 每个vptr指向对应基类的vtable
- 内存布局更复杂,可能需要指针调整
虚析构函数的重要性:
- 如果基类析构函数不是虚函数,通过基类指针delete派生类对象会出问题
- 只会调用基类析构函数,派生类的资源不会释放,导致内存泄漏
- 所以作为基类的类必须有虚析构函数
性能影响:
- 额外的内存开销:每个对象多一个vptr(
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
C++八股文全集 文章被收录于专栏
本专栏系统梳理C++技术面试核心考点,涵盖语言基础、面向对象、内存管理、STL容器、模板编程及经典算法。从引用指针、虚函数表、智能指针等底层原理,到继承多态、运算符重载等OOP特性从const、static、inline等关键字辨析,到动态规划、KMP算法、并查集等手写实现。每个知识点以面试答题形式呈现,注重原理阐述而非冗长代码,帮助你快速构建完整知识体系,从容应对面试官提问,顺利拿下offer。
