本文转载于https://www.zhihu.com/collection/81808301

  1. 进行一个简短的自我介绍。
  2. 你了解C++11的新特性吗?如何看待C++的发展?

    非常了解,垃圾语言毁我青春。

  3. 你了解C++的构建流程吗?

    各个编译单元分离编译,最后链接成为可执行文件。

  4. 如果把某函数的实现放在头文件中,会遇到链接错误提示某函数重复定义,这涉及到单一定义原则(ODR),请问如何解决这种问题?

    inline、static、namespace(匿名命名空间)、template

  5. inline关键词能对什么函数使用?

    任何函数,包括虚函数、constexpr函数、static函数、内联汇编函数。

    该函数必须在使用到的编译单元内存在定义。

  6. (接第4题)这四种解决方案都能解决函数定义在头文件时导致的重复定义链接错误,它们有什么区别?

    inline关键词声明的函数容许跨编译单元有重复定义,但是使用到的编译单元必须有定义,这些重复定义必须是等价的。

    static或匿名命名空间声明的函数在不同编译单元间表现为不等价的函数,若内部有静态局部变量则它们是不同的变量。

    模板函数与inline类似,唯一的不同在于模板函数允许某个使用到的编译单元没有定义,仅有前置声明。

    注意msvc允许inline函数仅有前置声明,而Clang遵循标准规定会报出链接错误。

  7. static在c++中有几种用法?

    静态成员变量、静态成员函数、静态局部变量、内部链接的函数

  8. (接第7题)多线程同时重入静态成员变量的初始化,能保证线程安全吗?用这种方式实现的单例模式是懒汉模式还是饿汉模式?

    能。懒汉模式。

  9. (接第5题)前面说了inline函数会在调用处展开,从而逻辑上会编译出多份函数代码。内联函数可以取得函数指针吗?出现在内联函数中的静态成员变量也会跟随调用点不同而变化吗?

    inline函数不保证在调用处展开,不加inline也不保证不会在调用处展开,inline关键词不会影响编译器对于函数展开的判断。因此内联函数可以取得函数指针。

    C++标准保证内联函数中的静态成员变量唯一。

  10. (接第5题)在类内定义的函数为隐式的内联函数,即使没有加上inline。而虚函数也可以在类内定义,如何理解内联的虚函数?

    (同第9题),或

    在静态绑定调用时允许在调用处展开,在动态绑定调用时与其他函数一致。

  11. C++的虚函数如何实现?

    虚函数表

  12. 一个类对象里面有几个虚函数表指针?

    与含有虚函数的基类数量相同。若没有含虚函数的基类,而该类本身有虚函数则有一个虚函数表指针。

  13. C++的对象的内存模型了解吗?各个基类对象和数据成员如何在内存中排布?
  14. 如果使用指向基类的this指针调用虚函数,而派生类中对应的虚函数应该接受指向派生类头部的this指针,如何进行this指针的转换?
  15. 许多人认为虚拟继承开销较大,请问虚拟继承有什么开销?如何避免?

    访问虚基类成员变量需要查虚基类表。

    避免方式:(1) 尽量不用虚拟继承,用模板链式继承替代;(2) 虚基类中不带有数据成员而只有虚函数,即作为接口。

  16. 了解虚析构函数吗?声明虚析构函数可以解决什么问题?如果不声明虚析构函数如何避免相应的问题?虚析构函数在虚函数表中如何存在?

    虚析构函数解决使用基类指针删除对象产生的问题。不声明虚析构函数可以使用shared_ptr管理内存。虚析构函数在虚函数表中成为scalar deleting destructorvector deleting destructor(析构代理函数),分别对应delete和delete[]操作符中调用析构函数和调用operator delete或operator delete[]两个操作。

  17. 若派生类重载了operator delete静态成员函数,使用基类指针删除对象时调用的是哪个operator delete?如何避免这种问题?

    如果没有使用虚析构函数,结果未定义。使用虚析构函数后编译器产生scalar deleting destructorvector deleting destructor析构代理函数,为operator delete提供多态性。

  18. 指针和引用有什么区别?

    (1) 指针可以为空;(2) 指针指向某个内存地址上的变量,而引用可以指代寄存器中的变量;(3) 指针解引用后一定是左值,而引用可以绑定右值;(4) 指针是对象类型,引用是引用类型;(5) 引用在定义时必须初始化,且不能改变绑定的变量。

  19. 你了解智能指针吗?智能指针有几种?

    标准库:shared_ptr、unique_ptr、weak_ptr、auto_ptr、exception_ptr

    其他常见:scoped_ptr、observer_ptr、intrusive_ptr

  20. shared_ptr和unique_ptr有什么区别?如何抉择智能指针和裸指针的使用?
  21. shared_ptr可以转换为shared_ptr,请问下之后如何保证正确释放内存?(不需要虚析构函数)

    控制块保存删除器和构造时使用的指针

  22. shared_ptr和unique_ptr都可以提供自定义删除器,有什么区别?

    shared_ptr的删除器提供类型擦除能力,unique_ptr的删除器类型必须显式指出

  23. shared_ptr的删除器/function是如何实现保存任意lambda函数的?

    类型擦除。定义基类接口后使用类模板保存函数对象后继承实现调用接口。

  24. 你了解设计模式吗?说说观察者模式(又叫做发布订阅模式),说说Qt中的signal slot机制。
  25. 观察者模式常用传递lambda作为回调函数。除了lambda以外,C++将类函数统称为可调用对象(invoke),请问下可调用对象包含哪些类型的函数或闭包?

    函数、函数指针、函数对象、lambda表达式、成员函数指针、成员变量指针

  26. std::function提供了更加通用的可调用对象类型擦除功能,可以一定程度上代替虚函数。请问应该如何进行取舍?

    爱用不用

  27. 你对函数了解吗?C++函数的调用会使用栈的数据结构,请问下传参、切换栈帧、分配局部变量、返回值等等过程具体如何利用栈实现?
  28. (接27)在x86上函数返回值会保存在eax和edx中,对于大对象而言会保存在栈上返回,请问具体如何实现?

    调用函数前在栈上预留返回值的空间,然后将返回值地址压栈传入函数参数。函数返回值存入该地址,函数返回清理栈后返回值恰好处于栈顶(即下一个局部变量的空间)

  29. (接28)在返回一个大对象的局部变量的时候需要手动调用std::move吗?如果调用/没调用会对性能有什么影响?

    考虑NRVO

  30. (接29)C++中有个名为返回值优化(即RVO)的技术,可以避免在函数返回过程中对于临时对象的拷贝。同时还有具名返回值优化(即NRVO)可以避免对局部变量的拷贝。请结合刚才栈上返回大对象的流程说一下这两个优化是如何运作的。
  31. Lambda表达式直接捕获this指针可能出现什么问题?如何解决?

    *this提前析构导致this成为野指针。用shared_ptr或者weak_ptr。

  32. 如果一个对象本身需要对外提供一个回调闭包,并且将自己的生命周期绑定到这个回调闭包上,如何获取this指针对应的shared_ptr?

    std::enable_shared_from_this

  33. enable_shared_from_this采用了一种名为CRTP的模板技术,可以实现静态多态。这和利用虚函数实现的动态多态有什么区别?
  34. 如果是类模板A继承enable_shared_from_this,此时访问模板基类的成员必须加上this指针或者名称限定符,为什么要这么做?

    C++模板的待决名/依赖名规则(dependent name)

  35. 编写模板函数的时候常常会遇到一些分支需要处理,例如对于sort,如果迭代器iter是随机访问迭代器执行算法A,如果iter是双向迭代器执行算法B,否则执行算法C,请问如何实现?

    标签派发法、enable_if、if constexpr均可

  36. 进一步的,如何判断一个类里面是否有名为to_string()的非静态成员函数?

    表达式SFINAE

  37. shared_ptr线程安全吗?如果需要多线程同时读写同一个shared_ptr变量需要如何处理?
  38. 多线程中经常出现某个对象仍在使用但是被其他线程提前析构的情况,如何设计以避免这种情况的发生?

    使用shared_ptr

  39. 协程和线程,异步和同步有什么联系和区别?
  40. 如何从std::vector删除所有满足某个条件的元素?std::list呢?

    erase-remove惯用法,remove成员函数

  41. List的底层实现为双向链表,其迭代器只有一个指针的大小。如何设计以满足

    *(end()-1)==back()且空链表时begin() == end(),且不分配堆内存

    使用循环列表,last->next和first->prev指向std::list对象本身

  42. 如何遍历map?编写范围for循环时需要填写什么元素类型?

    auto &

    std::map<T1, T2>::value_type &或std::map<T1, T2>::reference_type

    std::pair &

  43. 一般对于非预期的情况我们有三种处理方法:断言、异常、错误码。请问如何取舍和选择?
  44. 函数抛出异常常常伴随着潜在的返回路径没有被注意到,造成资源泄露。请问C++中如何实现类似于java语言内的finally机制?

    析构函数

  45. C++抛出异常并被捕获的过程中,被抛出的异常对象会被放置在哪里?按值捕获和按左值引用捕获有什么区别?为什么不能按右值引用捕获异常?

    放置在未指明的存储中(大部分情况在堆上)。按值捕获会造成异常对象切片。异常对象是左值。

  46. 使用new表达式在堆上创建对象时可能会抛出哪几种异常?

    构造函数、std::bad_alloc、std::bad_array_length等

  47. 在C++中一个对象的基类子对象和各种成员的初始化顺序是什么样的?没有写在成员初始化列表中的成员会被初始化吗?

    与类定义中成员变量的声明顺序一致,和成员初始化列表中的顺序无关

  48. 构造函数抛出异常的时候,会调用析构函数吗?new表达式先分配后构造的过程中如果构造抛出异常会释放已经分配的内存吗?

    不会(代理构造函数除外)。会。

  49. 拷贝构造函数和拷贝赋值运算符有什么区别?编写一个vector的拷贝赋值运算符,如何实现强异常安全?

    区别略。使用copy-and-swap惯用法

  50. (接48题)按照你的说法,分配内存失败时需要抛出bad_alloc异常,而抛出异常的过程需要分配内存,编译器和操作系统是如何解决这种递归依赖的?

    (1) 一开始在内存中预留一个std::bad_alloc异常对象以免实际抛出时内存不足;(2) 内存不足导致无法抛出该异常时大部分情况下已经宕机,不会出现递归;(3) 实际运行中操作系统会尽可能满足需求,基本不抛出std::bad_alloc。

注:部分答案具有主观性,仅供参考~~~

最后修改:2024 年 07 月 17 日
如果觉得我的文章对你有用,请随意赞赏