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。

全部评论

相关推荐

点赞 评论 收藏
分享
评论
1
3
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务