1. 三种多态的实现方式及其区别
静态多态和动态多态:函数重载、运算符重载、泛型编程与模板(特别的CRTP)(编译期多态);虚函数 动态多态
2. STL 容器在 for 循环中使用 erase 删除元素会导致什么问题?
1 | std::vector<int> vec = {1, 2, 3, 4, 5}; |
3. 什么是Perfec t Forwarding?为什么要使用它,它解决了什么问题?写一下
在函数模板中,保持参数的原有类型和特性(const)进行转发。没有完美转发时,泛型编程会遇到参数类别丢失的问题。传入右值时,T会被推导成非右值,const属性也会消失;
1 | template<typename T> |
4. 完美转发和移动语义的实践场景
日志函数、装饰器模式、容器构造器
1 | template<typename T> |
5. 虚函数表的存储位置和内存结构
vtable 只读数据段、vptr对象内部
6. 单继承、多继承、虚继承的vptr是怎样的?对应的C++内存模型是怎样的
单继承:对象内通常只有一个 vptr,指向该类的 vtable;基类子对象在对象起始位置,内存布局相对简单。
多继承:对象内包含多个基类子对象,每个含虚函数的基类子对象各自有一个 vptr;指针向上转型时需要调整 this 指针偏移。
虚继承:为共享虚基类子对象,对象内增加 vbptr 或相关偏移信息(实现相关);虚基类子对象位置通常不固定,需要通过表或偏移计算定位。
7. 虚函数能否是模板函数?为什么?
虚函数不能是模板函数。虚函数表要求在编译器固定布局,模板函数实例化在编译器按需生成。时机冲突
8. 析构函数声明为虚函数的原因是什么?
当一个派生类对象通过 基类指针 删除时,如果基类的析构函数 不是虚的,将只会调用基类析构函数,而派生类部分不会被析构,造成 资源泄漏 或 未定义行为。
9. C++类默认生成的成员函数有哪些?
默认构造、拷贝、析构、移动、拷贝赋值与移动赋值
10. 左值和右值的区别与应用场景
左值有可识别的存储位置、可取地址;右值通常是临时值、表达式结果或将亡值。
应用:
- 左值用于持久对象传参、可多次使用的对象。
- 右值用于移动语义与资源转移,避免深拷贝。
- 通过重载
T&和T&&实现复制与移动的区分。
11. Name Mangling的作用及其带来的兼容性问题
作用:将函数名、参数类型、命名空间等编码到符号名中,实现重载和模板实例的链接。
问题:不同编译器/版本的编码规则不同,导致 ABI 不兼容;跨语言调用需用 extern "C" 关闭改名。
12. 动态库和静态库的优缺点对比
静态库:
- 优点:发布单一可执行文件,部署简单,运行时无额外依赖。
- 缺点:可执行文件体积大;库更新需要重新链接。
动态库:
- 优点:复用共享,体积小,可独立更新。
- 缺点:部署依赖复杂;加载/符号解析有额外开销;ABI 兼容要求高。
13. 什么是智能指针?什么是RAII?既然如此,全部用栈区不就可以了,为什么要堆区? 或者全部用堆区可以了,为什么要栈区?
堆适用 大小不确定,并且可以利用零散碎片,栈不行;栈适合函数,天生紧凑,堆不行;两者协作处理会使得内存更加紧凑高效。
工厂模式的一个优点:唯一化管理,比如LLVM中的Type类型管理
14. vector有什么缺点,为什么要有pvector?或者问移动构造的适用场景(如gapbs)
vector 缺点:
- 扩容需要搬迁元素,可能触发大量移动/拷贝。
- 迭代器/引用在扩容后失效。
- 频繁小对象插入删除成本高。
pvector 常用于“指针向量”或“稳定地址”需求:
- 保存指针或小对象句柄,扩容时仅移动指针,避免大对象搬迁。
- 或用自定义分配器/分段存储,提高稳定性与局部性。
移动构造适用场景:
- 临时对象返回值优化不足时的资源转移。
- 容器扩容或重排时把资源从旧对象“搬走”,降低深拷贝成本。
15. 【打卡题】const与指针和引用
常见组合:
const int* p/int const* p:指向常量的指针,不能改 *p,可改 p。int* const p:指针常量,不能改 p,可改 *p。const int* const p:指向常量的常量指针。const int& r:可绑定临时对象,延长生命周期,但不可修改所指对象。
16. 【打卡题】宏定义 define 和 const 常量
- 宏是预处理期文本替换,无类型检查,调试困难,易产生副作用。
const有类型和作用域,受编译器检查,可被调试器识别;通常优先用const/constexpr。
17. new 和malloc的区别
new:分配内存并调用构造函数;失败抛std::bad_alloc。malloc:仅分配原始内存;失败返回nullptr。delete/delete[]与free配套,不可混用。new支持重载和自定义分配器。
18. atomic原子操作为什么比读写锁更快?
1.硬件指令实现
atomic原子操作是硬件级别的,直接在CPU指令和缓存层完成,会通过特定的硬件指令直接锁CPU的缓存行和内存总线,确保其他核心无法访问同一块内存。
而读写锁则是软件级别的,需要内核系统调用实现,基于互斥锁机制实现,线程无法获取锁时会通过系统调用将自己放入等待队列,涉及用户态内核态切换,导致比成本比硬件实现高出几个数量级。
19. vector和dequeue的区别,为什么遍历vector的效率更高。
vector 连续内存,deque 分段连续(块数组 + 小块)。
遍历 vector 更快的原因:
- 连续内存更利于缓存命中和预取。
- 迭代器计算简单;
deque需要跨块索引。
20. 怎样实现简化版本的智能指针?
核心要点:RAII + 禁止拷贝 + 支持移动。
1 | template <typename T> |
21. 多态是的原理?C++内存对象模型。
原理:通过 vptr 指向 vtable,在运行期根据对象实际类型分发虚函数调用。
对象模型(简化):
- 对象内含数据成员 + vptr。
- vtable 是函数指针数组(含 RTTI 指针等实现细节)。
- 基类指针指向派生类对象时,vptr 指向派生类 vtable。
22. 能否允许在构造函数中调用虚函数?会生效吗
语法允许,但构造/析构期间不会发生动态分派:调用的是当前构造层级的实现。
原因:对象尚未构造完整,vptr 指向当前类的 vtable,派生类部分未初始化。
23. 内存对齐规则
常见规则:
- 结构体成员按自身对齐要求对齐。
- 结构体整体对齐为成员最大对齐值。
- 末尾可能填充以满足整体对齐。
- 可用
alignas/alignof查询或指定对齐。
24.空类几个字节?为什么
通常为 1 字节,保证不同对象具有不同地址;若有继承或空基类优化(EBO),可能不占额外空间。
25.常量指针和指针常量是什么?this指针是什么指针?
- 常量指针:
const T* p,指向常量,不能改 *p。 - 指针常量:
T* const p,指针值不可改。 this是指向当前对象的指针,非静态成员函数隐式参数;在const成员函数中类型为const T* const。
26.explicit 的作用
防止构造函数或转换运算符发生隐式类型转换,避免意外构造。
1 | struct X { explicit X(int) {} }; |
27.extern 的作用
声明外部链接符号,告诉编译器该符号定义在别处;多文件共享全局变量或函数。与 extern "C" 配合可关闭 C++ 名字改编。
28.避免虚函数调用的开销?
CRTP(奇异递归模板模式):在编译时进行静态多态,避免虚函数表(vtable)查询和间接调用开销。
final 关键字:如果类不会被继承,将类或虚函数标记为 final,允许编译器去虚拟化(devirtualization),直接调用函数。
手工查表分发:用函数指针数组或 switch-case 替代虚函数,虽然灵活性稍低,但开销更确定。
29.Python与C++交互时,用过pybind11或ctypes吗?遇到过GIL问题吗?
pybind11:现代首选。基于C++11,自动处理类型转换,可轻松暴露C++类、函数、枚举,甚至支持NumPy数组的零拷贝访问。
ctypes:用于调用C库,需手动编写外部函数接口,较繁琐。
Cython:适合有大量数值计算且需要接近C性能的场景。
** GIL(全局解释器锁):**
现象:Python中多线程执行CPU密集型C++代码时,因为GIL的存在,同一时刻只有一个线程能执行Python字节码,导致无法利用多核。
解决:在C++函数中调用 py::gil_scoped_release 临时释放GIL,执行完耗时计算后再用 py::gil_scoped_acquire 重新获取。注意:释放GIL期间不能操作任何Python对象。
替代方案:使用 std::thread + pybind11::call_guardpy::gil_scoped_release,让C++线程完全脱离GIL运行。
30.实现一个内存池,如何减少碎片并提升算子执行效率?
设计要点:
- 分级空闲链表(size class),按块大小分桶,减少外部碎片。
- 大块用 slab/arena 分配,避免频繁系统调用。
- 线程本地缓存(tcache)降低锁竞争。
- 对齐与批量回收,提高缓存友好性。
- 对热点算子预分配,复用内存,减少峰值抖动。