昆仑天工 - C++软件开发 二面
1. 自我介绍,重点说说你对C++的理解和项目经验
(开放性问题,候选人应结合一面反馈,突出技术深度和项目亮点,展示对C++的理解不只停留在语法层面)
2. C++的内存模型是什么?happens-before关系如何理解?
参考答案:C++11引入了正式的内存模型,解决多线程下的可见性和顺序问题:
- 内存模型的作用:定义多线程程序中内存操作的可见性和顺序规则,屏蔽不同硬件架构的差异
- happens-before关系:如果操作A happens-before操作B,则A的结果对B可见,且A在B之前执行
- 建立happens-before的方式:同一线程内的顺序执行、mutex的unlock happens-before后续的lock、原子操作的release happens-before后续的acquire、线程创建happens-before线程内的操作
- 顺序一致性:最强的内存序,所有线程看到的操作顺序完全一致,但性能开销最大
- 宽松内存序:只保证原子性不保证顺序,性能最好但编程最复杂
- acquire-release语义:release写操作之前的所有写对acquire读之后的所有读可见,是最常用的平衡方案
- 实际意义:理解内存模型才能正确编写无锁代码,避免数据竞争和未定义行为
3. 什么是协程?C++20的协程和线程有什么区别?
参考答案:协程是一种用户态的轻量级并发机制:
- 协程本质:可以暂停和恢复执行的函数,暂停时保存执行状态,恢复时从暂停点继续
- 与线程的区别: 调度方式:线程由操作系统内核调度,协程由程序自己调度(用户态)切换开销:线程切换需要保存大量寄存器和上下文,开销大;协程切换只保存少量状态,开销极小内存:每个线程有独立的栈(通常1-8MB),协程栈很小(KB级别),可以创建大量协程并发模型:线程是抢占式并发,协程是协作式并发(主动让出控制权)线程安全:多线程需要加锁,协程在单线程内执行天然无竞争
- C++20协程关键字:co_await(等待异步操作)、co_yield(产出值并暂停)、co_return(结束协程)
- 适用场景:高并发IO密集型任务(网络服务器、爬虫)、异步编程简化(避免回调地狱)、生成器模式
- 与async/future的区别:async创建真实线程或线程池任务,协程是单线程内的异步,不涉及线程切换
4. 模板元编程中的SFINAE是什么?有什么实际用途?
参考答案:SFINAE(Substitution Failure Is Not An Error)是C++模板的核心机制:
- 原理:模板参数替换失败时,编译器不报错,而是将该模板从候选集中移除,继续尝试其他重载
- 作用:在编译期根据类型特性选择不同的函数实现,实现编译期的条件分支
- 常见用途: 类型检测:判断类型是否有某个成员函数,根据结果选择不同实现约束模板参数:只允许特定类型使用某个模板(如只允许整数类型)特化选择:为不同类型提供不同的优化实现
- enable_if:SFINAE最常用的工具,条件为true时提供类型,条件为false时替换失败
- C++17的if constexpr:更直观的编译期条件分支,在很多场景可以替代SFINAE
- C++20的concepts:更清晰的模板约束语法,是SFINAE的现代替代方案,错误信息更友好
- 实际价值:STL内部大量使用SFINAE,如std::enable_if、type_traits库,理解它有助于读懂标准库源码
5. 如何设计一个线程池?需要考虑哪些问题?
参考答案:线程池是服务器开发的基础组件,设计时需要全面考虑:
- 核心组成:工作线程数组、任务队列、同步机制(互斥锁+条件变量)、线程池状态管理
- 基本工作流程:初始化时创建固定数量的工作线程,工作线程循环从任务队列取任务执行,提交任务时加入队列并通知工作线程
- 线程数量选择:CPU密集型任务设置为CPU核心数,IO密集型任务可以设置更多(核心数的2-4倍),混合型任务需要测试调优
- 任务队列设计:使用互斥锁保护队列、条件变量实现等待通知、支持任务优先级、设置队列最大长度防止内存耗尽
- 动态扩缩容:监控队列长度和线程利用率,队列积压时增加线程,线程空闲时减少线程,设置最小和最大线程数
- 优雅关闭:停止接受新任务、等待已提交任务执行完毕、通知所有线程退出、join所有线程
- 任务返回值:使用std::future和std::promise获取异步任务结果,submit函数返回future
- 异常处理:工作线程捕获任务抛出的异常,通过future传递给调用方,防止线程因异常退出
- 工作窃取:高级优化,每个线程有自己的本地队列,空闲时从其他线程队列窃取任务,提高负载均衡
6. 红黑树和B+树的区别?数据库为什么用B+树而不用红黑树?
参考答案:两者都是平衡树,但设计目标不同:
- 红黑树:内存中的平衡二叉搜索树,节点最多两个子节点,树高O(log n),查找插入删除都是O(log n)
- B+树:多路平衡搜索树,每个节点可以有多个子节点(由阶数决定),所有数据存在叶子节点,叶子节点形成有序链表
- 数据库选择B+树的原因: 磁盘IO是瓶颈:数据库数据存在磁盘,每次IO读取一个页(通常16KB)。B+树节点可以存储更多键值,树更矮,IO次数更少红黑树树高更大:百万数据红黑树高约20层,B+树只需3-4层,IO次数差距巨大范围查询高效:B+树叶子节点链表支持高效范围查询,红黑树范围查询需要中序遍历效率低缓存友好:B+树内部节点只存键不存数据,一个页能存更多键,缓存命中率更高叶子节点存全量数据:非叶子节点只存索引,叶子节点存完整数据,查询路径固定
- 内存场景用红黑树:内存访问无IO瓶颈,红黑树实现简单,STL的map/set就用红黑树
7. 什么是一致性哈希?它解决了什么问题?
参考答案:一致性哈希是分布式系统中解决数据分布问题的经典算法:
- 普通哈希的问题:节点数量变化时,几乎所有数据都需要重新映射,导
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
C++八股文全集 文章被收录于专栏
本专栏系统梳理C++技术面试核心考点,涵盖语言基础、面向对象、内存管理、STL容器、模板编程及经典算法。从引用指针、虚函数表、智能指针等底层原理,到继承多态、运算符重载等OOP特性从const、static、inline等关键字辨析,到动态规划、KMP算法、并查集等手写实现。每个知识点以面试答题形式呈现,注重原理阐述而非冗长代码,帮助你快速构建完整知识体系,从容应对面试官提问,顺利拿下offer。
查看5道真题和解析