跳到主要内容
2025 C++ 大厂面试高频问题与解析 | 极客日志
C++ 算法
2025 C++ 大厂面试高频问题与解析 涵盖 46 道 C++ 大厂面试高频题目,涉及内存管理、虚函数机制、RAII、const 与 constexpr 区别、volatile 与 atomic 线程安全、auto 与 decltype 推导规则、菱形继承与虚继承、RTTI 使用、三法则五法则零法则、虚函数性能优化、缓存友好代码设计、多态方案对比、异常处理与栈展开、多线程异常安全、析构函数异常处理、函数重载决议、模板实例化、值类别与移动语义、智能指针实现原理、Lambda 捕获与悬空引用、std::function 类型擦除、STL 容器扩容与哈希冲突、SFINAE 与变参模板、C++20 概念与协程、异步编程与内存序等核心知识点。提供详细代码示例与解析,帮助开发者系统掌握 C++ 底层原理与工程实践,应对技术面试挑战。
灵魂伴侣 发布于 2026/3/16 更新于 2026/5/2 13 浏览1. 请解释以下代码中各个变量存储在哪个内存区域,并说明原因。【腾讯 - 后台开发】
#include <iostream>
const int g_const = 10 ;
int g_var = 20 ;
static int s_var = 30 ;
char * p_str = "Hello" ;
void memory_layout_demo () {
static int local_s_var = 40 ;
int local_var = 50 ;
const int local_const = 60 ;
int * heap_var = new int (70 );
char arr[] = "World" ;
std::cout << "g_const: " << &g_const << std::endl;
std::cout << "g_var: " << &g_var << std::endl;
std::cout << "s_var: " << &s_var << std::endl;
std::cout << "p_str: " << &p_str << " -> " << (void *)p_str << std::endl;
std::cout << "local_s_var: " << &local_s_var << std::endl;
std::cout << << &local_var << std::endl;
std::cout << << &local_const << std::endl;
std::cout << << &heap_var << << heap_var << std::endl;
std::cout << << &arr << std::endl;
heap_var;
}
{
();
;
}
"local_var: "
"local_const: "
"heap_var: "
" -> "
"arr: "
delete
int main ()
memory_layout_demo
return
0
g_const:存储在常量区,因为是 const 全局常量
g_var:存储在.data 段,已初始化的全局变量
s_var:存储在.data 段,静态全局变量
p_str:指针本身在.data 段,指向的字符串"Hello"在常量区
local_s_var:存储在.data 段,静态局部变量
local_var:存储在栈,普通局部变量
local_const:存储在栈,const 局部变量
heap_var:指针本身在栈,指向的内存地址在堆
arr:存储在栈,数组在栈上分配空间
2. 为什么在构造函数和析构函数中调用虚函数不会发生多态?请从 vptr 的初始化时机解释。【字节跳动 - 基础架构】 参考答案:
在构造函数中,vptr 的初始化发生在构造函数体执行之前。当基类构造函数执行时,vptr 指向基类的虚函数表,因此调用的虚函数是基类的版本。即使后续派生类的构造函数会重新设置 vptr 指向派生类的虚函数表,但在基类构造函数执行期间,多态机制还没有完全建立。
同样地,在析构函数中,当派生类的析构函数执行完毕后,vptr 会被重新设置为指向基类的虚函数表,然后在基类析构函数中调用虚函数时,只能调用到基类的版本。
这是一种安全机制,确保在对象构造和析构的不完整状态下,不会调用到尚未初始化或已经销毁的派生类成员。
3. 请实现一个简单的 RAII 包装类,用于管理使用 malloc 分配的内存,确保内存不会泄漏。【百度 - 智能驾驶】 #include <iostream>
#include <cstdlib>
class MallocRAII {
public :
explicit MallocRAII (size_t size) : ptr_(malloc(size)) {
if (!ptr_) {
throw std::bad_alloc ();
}
std::cout << "Allocated " << size << " bytes at " << ptr_ << std::endl;
}
void * get () const { return ptr_; }
void * operator ->() const { return ptr_; }
MallocRAII (const MallocRAII&) = delete ;
MallocRAII& operator =(const MallocRAII&) = delete ;
MallocRAII (MallocRAII&& other) noexcept : ptr_ (other.ptr_) {
other.ptr_ = nullptr ;
}
MallocRAII& operator =(MallocRAII&& other) noexcept {
if (this != &other) {
free (ptr_);
ptr_ = other.ptr_;
other.ptr_ = nullptr ;
}
return *this ;
}
~MallocRAII () {
if (ptr_) {
std::cout << "Freeing memory at " << ptr_ << std::endl;
free (ptr_);
}
}
private :
void * ptr_;
};
void malloc_raii_demo () {
try {
MallocRAII memory (100 ) ;
int * data = static_cast <int *>(memory.get ());
data[0 ] = 42 ;
std::cout << "Data: " << data[0 ] << std::endl;
} catch (const std::bad_alloc& e) {
std::cerr << "Memory allocation failed: " << e.what () << std::endl;
}
}
int main () {
malloc_raii_demo ();
return 0 ;
}
在构造函数中分配内存,析构函数中释放内存
处理分配失败的情况(抛出异常)
禁止拷贝构造和拷贝赋值(避免重复释放)
提供移动语义支持
提供访问原始指针的方法
异常安全性保证
4. 请解释 const 和 constexpr 的区别,并说明在什么情况下应该使用 constexpr 而不是 const。【腾讯 - 微信后台】
语义不同:const 表示"只读",而 constexpr 表示"编译期常量"
计算时机:const 可以在运行时计算,constexpr 必须在编译期计算
应用范围:const 可以修饰变量、函数参数、成员函数等,constexpr 主要修饰变量和函数
C++ 版本:const 来自 C 语言,constexpr 是 C++11 引入的
需要编译期常量的场合(数组大小、模板参数、case 标签等)
定义可以在编译期计算的数学常量
构造编译期可知的对象
用于元编程和模板计算
运行时常量
函数参数保护
const 成员函数
指针和引用的常量性修饰
5. 请解释 volatile 和 atomic 的区别,并说明在什么情况下应该使用 volatile 而不是 atomic。【字节跳动 - 基础架构】
语义不同:volatile 防止编译器优化,保证每次访问都从内存读写;atomic 保证操作的原子性
线程安全:volatile 不保证原子性,atomic 保证原子性
内存顺序:volatile 没有内存顺序保证,atomic 提供内存顺序控制
适用场景:volatile 用于硬件寄存器、内存映射 IO 等;atomic 用于多线程同步
内存映射的硬件寄存器访问
被信号处理程序修改的变量
在多核系统中被其他 CPU 修改的共享内存
防止编译器优化掉"无效果"的代码
多线程间的数据共享和同步
需要原子操作的计数器、标志位等
实现无锁数据结构
需要内存顺序控制的场景
重要提示:在现代 C++ 多线程编程中,应该优先使用 atomic 而不是 volatile 来保证线程安全。
6. 请解释 auto 和 decltype 的推导规则有什么不同,并举例说明在什么情况下应该使用 decltype 而不是 auto。【百度 - 智能云】
auto:使用模板参数推导规则,忽略顶层 const 和引用
decltype:返回表达式的确切类型,包括 const 和引用限定符
需要精确类型匹配时:当需要保持表达式的完整类型信息(包括 const 和引用)
函数返回类型推导:在 trailing return type 中根据参数推导返回类型
模板元编程:需要查询表达式类型进行编译期计算
decltype(auto):用于完美转发函数返回值类型
const int & get_value () ;
auto a = get_value ();
decltype (auto ) da = get_value ();
template <typename T, typename U> auto multiply (T t, U u) -> decltype (t * u) {
return t * u;
}
7. 请画出以下代码中 Derived 类的内存布局图,并解释虚函数表的结构。【腾讯 - 微信后台】 class A {
public :
virtual void f1 () {}
int a = 1 ;
};
class B : public A {
public :
virtual void f2 () {}
int b = 2 ;
};
class C : public A {
public :
virtual void f3 () {}
int c = 3 ;
};
class D : public B, public C {
public :
virtual void f4 () {}
int d = 4 ;
};
参考答案:
D 对象包含两个虚函数指针:一个指向 B 的虚表(包含 f1, f2, f4),一个指向 C 的虚表(包含 f1, f3)。存在两个 A 子对象,这是菱形继承的问题。
8. 请解释虚继承是如何解决菱形继承问题的,并分析其带来的性能开销。【字节跳动 - 基础架构】
共享基类子对象:虚继承确保在菱形继承 hierarchy 中,虚基类只有一个共享的实例,而不是每个中间类都有自己的基类副本。
通过指针间接访问:编译器通过额外的指针(vptr 或 offset 指针)来访问共享的虚基类子对象。
调整对象布局:虚继承的对象布局更复杂,包含指向共享基类的指针或偏移量信息。
对象大小增加:需要额外的存储空间来维护虚基类指针或偏移量信息。
访问速度降低:通过指针间接访问虚基类成员,比直接访问多一次内存寻址。
构造顺序复杂:虚基类的构造由最派生类负责,增加了构造函数的复杂性。
缓存不友好:间接访问可能导致缓存未命中,影响性能。
使用建议:只有在真正需要解决菱形继承问题时才使用虚继承,因为它会带来显著的性能和复杂性开销。对于接口继承,通常使用普通多重继承即可。
9. 请解释 dynamic_cast 的工作原理,并分析在什么情况下应该避免使用 RTTI。【百度 - 智能驾驶】
类型信息查询:通过对象的虚函数表找到其 RTTI 信息
类型层次遍历:检查目标类型是否在对象的继承层次中
指针调整:对于多重继承,调整 this 指针到正确的子对象位置
返回结果:如果转换合法返回正确指针,否则返回 nullptr(对于指针)或抛出 bad_cast(对于引用)
性能关键代码:RTTI 操作有显著性能开销
嵌入式系统:RTTI 可能占用额外空间,某些嵌入式环境禁用
需要二进制兼容性:RTTI 实现可能编译器相关
设计层面:过度使用 RTTI 可能表明糟糕的面向对象设计
虚函数多态:用虚函数代替类型检查
访问者模式:用于复杂的类型层次遍历
手动类型标识:简单的 enum 类型标识
类型安全的转换:使用 static_cast 加上设计保证
10. 请解释 C++ 中的三法则、五法则和零法则,并说明在现代 C++ 开发中应该遵循哪个法则。【腾讯 - 游戏客户端】 参考答案:
三法则 (Rule of Three):
如果一个类需要自定义析构函数、拷贝构造函数或拷贝赋值运算符中的任何一个,那么它很可能需要全部三个。适用于需要手动管理资源的类。
五法则 (Rule of Five):
在三法则基础上增加移动构造函数和移动赋值运算符。适用于 C++11 及以后版本,需要支持移动语义的类。
零法则 (Rule of Zero):
类不应该自定义任何特殊成员函数(析构函数、拷贝/移动构造、拷贝/移动赋值),而是依赖编译器自动生成的行为。通过使用智能指针、标准库容器等资源管理类来避免手动资源管理。
优先遵循零法则:使用标准库组件管理资源,让编译器自动生成正确的特殊成员函数。
必要时使用五法则:当确实需要自定义资源管理时,遵循五法则并提供
11. 请分析虚函数调用的性能开销来源,并给出三种优化虚函数性能的方案。【阿里巴巴 - 基础设施】
间接跳转开销:需要通过虚函数表进行间接函数调用
缓存不友好:虚函数表可能不在缓存中,导致缓存未命中
内联限制:虚函数通常无法被内联优化
分支预测失败:间接跳转可能干扰 CPU 的分支预测
使用 final 关键字:标记不需要进一步重写的虚函数,允许编译器进行去虚拟化优化
CRTP 模式:使用静态多态替代动态多态,完全避免虚函数开销
手动虚函数表:针对性能关键代码,使用函数指针表替代虚函数机制
数据导向设计:按类型组织数据,减少虚函数调用频率
12. 请解释什么是缓存友好代码,并举例说明如何优化 C++ 对象的内存布局以提高缓存命中率。【华为 - 系统开发】 参考答案:
缓存友好代码:
缓存友好代码是指能够有效利用 CPU 缓存层次结构,减少缓存未命中次数的代码。关键特征包括:
顺序访问模式
数据局部性好
避免随机内存访问
适当的内存对齐
数据分组:将频繁一起访问的数据成员放在一起
冷热分离:将频繁访问的"热"数据和不常访问的"冷"数据分开
适当填充:使用 alignas 确保关键数据跨越缓存行边界
面向数据设计:使用 SoA(Structure of Arrays)代替 AoS(Array of Structures)
struct Particle {
Vec3 position;
Vec3 velocity;
int id;
time_t create_time;
};
struct ParticleSystem {
std::vector<Vec3> positions;
std::vector<Vec3> velocities;
std::vector<int > ids;
std::vector<time_t > create_times;
};
13. 请比较基于继承的多态和基于 std::variant 的多态各自的优缺点,并说明在什么场景下应该选择哪种方案。【谷歌 - 系统架构】
经典的面向对象设计,概念清晰
支持动态扩展,容易添加新的子类
良好的封装性,实现细节隐藏
成熟的工具链支持(调试、序列化等)
缺点:
性能开销(虚函数调用、对象切片)
内存布局分散,缓存不友好
需要指针或引用语义
菱形继承问题复杂
值语义,避免指针和生命周期管理
内存局部性好,缓存友好
编译时类型安全,无运行时类型错误
性能更好(无虚函数开销)
缺点:
需要提前知道所有可能类型
添加新类型需要修改 variant 定义
访问逻辑可能变得复杂(visitor 模式)
C++17 才完全支持
选择继承多态:当类型集合需要动态扩展、需要良好的封装性、或者使用现有面向对象框架时
选择 variant 多态:当类型集合固定、性能要求高、需要值语义、或者希望避免虚函数开销时
混合使用:在大型系统中,可以根据不同模块的需求混合使用两种方案
14. 请解释 C++ 异常处理机制中栈展开(stack unwinding)的过程,以及在什么情况下会发生资源泄漏?【腾讯 - 后台开发】
异常抛出:当 throw 语句执行时,当前函数停止执行,开始栈展开过程
局部对象析构:从当前函数开始,按照创建的反序析构所有局部对象
查找 catch 块:沿着调用栈向上查找匹配的 catch 块
匹配处理:找到匹配的 catch 块后执行处理代码
恢复执行:处理完成后继续执行 catch 块之后的代码
非 RAII 管理的资源:使用裸指针、文件句柄等资源而没有用 RAII 包装
析构函数中抛出异常:如果在栈展开过程中析构函数又抛出异常,程序会调用 std::terminate
异常不匹配:没有合适的 catch 块捕获异常,导致 std::terminate 被调用
动态内存泄漏:使用 new 分配内存但在异常抛出前没有 delete
void unsafe_function () {
int * ptr = new int (42 );
some_operation_that_may_throw ();
delete ptr;
}
void safe_function () {
std::unique_ptr<int > ptr = std::make_unique <int >(42 );
some_operation_that_may_throw ();
}
15. 在多线程环境中,如何保证异常安全性?请考虑锁、资源管理和状态一致性。【华为 - 系统开发】
锁的获取和释放必须正确,避免死锁
资源管理需要线程安全
状态一致性需要跨线程保证
#include <iostream>
#include <mutex>
#include <memory>
#include <vector>
class ThreadSafeContainer {
public :
void add_value (int value) {
std::lock_guard<std::mutex> lock (mutex_) ;
auto new_data = std::make_shared<std::vector<int >>(*data_);
new_data->push_back (value);
data_.swap (new_data);
}
std::vector<int > get_values () const {
std::lock_guard<std::mutex> lock (mutex_) ;
return *data_;
}
void add_values (const std::vector<int >& values) {
std::lock_guard<std::mutex> lock (mutex_) ;
auto new_data = std::make_shared<std::vector<int >>(*data_);
new_data->insert (new_data->end (), values.begin (), values.end ());
data_.swap (new_data);
}
private :
mutable std::mutex mutex_;
std::shared_ptr<std::vector<int >> data_ = std::make_shared<std::vector<int >>();
};
void multi_thread_safety_demo () {
ThreadSafeContainer container;
container.add_value (1 );
container.add_value (2 );
container.add_values ({3 , 4 , 5 });
auto values = container.get_values ();
for (int val : values) {
std::cout << val << " " ;
}
std::cout << std::endl;
}
RAII 锁管理:使用 std::lock_guard 或 std::unique_lock 确保锁的正确释放
副本操作:在锁的保护下操作副本,确保强异常安全
原子交换:使用无异常操作交换状态
返回副本:读取操作返回数据副本,避免持有锁时进行复杂操作
16. 为什么在析构函数中抛出异常会导致程序调用 std::terminate?请从 C++ 异常处理机制的角度解释,并给出安全的析构函数实现方案。【腾讯 - 微信后台】
栈展开冲突:当异常处理过程中进行栈展开时,如果析构函数又抛出新异常,C++ 无法处理这种"异常中的异常"场景
不确定性:两个异常同时存在会导致程序状态不确定,无法保证资源正确释放
标准规定:C++ 标准规定,析构函数在栈展开过程中抛出异常会调用 std::terminate
class SafeResourceManager {
public :
SafeResourceManager () : resource_ (acquire_resource ()) {}
~SafeResourceManager () noexcept {
try {
release_resource (resource_);
} catch (const std::exception& e) {
std::cerr << "资源释放失败:" << e.what () << std::endl;
emergency_cleanup (resource_);
} catch (...) {
std::cerr << "未知资源释放错误" << std::endl;
emergency_cleanup (resource_);
}
}
SafeResourceManager (const SafeResourceManager&) = delete ;
SafeResourceManager& operator =(const SafeResourceManager&) = delete ;
private :
Resource* resource_;
Resource* acquire_resource () { }
void release_resource (Resource* res) { }
void emergency_cleanup (Resource* res) noexcept { }
};
17. 在多线程环境中,如果析构函数需要执行可能失败的操作,应该如何设计才能保证线程安全和异常安全?【阿里巴巴 - 中间件】 #include <iostream>
#include <mutex>
#include <condition_variable>
#include <atomic>
class ThreadSafeDestructor {
public :
ThreadSafeDestructor () : destroyed_ (false ) {}
~ThreadSafeDestructor () noexcept {
std::lock_guard<std::mutex> lock (mutex_) ;
destroyed_ = true ;
try {
perform_cleanup ();
condition_.notify_all ();
} catch (const std::exception& e) {
std::cerr << "清理操作失败:" << e.what () << std::endl;
emergency_cleanup ();
} catch (...) {
std::cerr << "未知清理错误" << std::endl;
emergency_cleanup ();
}
}
void safe_operation () {
std::unique_lock<std::mutex> lock (mutex_) ;
condition_.wait (lock, [this ] { return !destroyed_; });
if (destroyed_) {
throw std::runtime_error ("对象已被销毁" );
}
perform_operation ();
}
private :
mutable std::mutex mutex_;
std::condition_variable condition_;
std::atomic<bool > destroyed_;
void perform_operation () { }
void perform_cleanup () { }
void emergency_cleanup () noexcept { }
};
void thread_safe_destructor_demo () {
ThreadSafeDestructor manager;
std::thread t1 ([&] {
try { manager.safe_operation(); }
catch (const std::exception& e) { std::cerr << "线程 1 错误:" << e.what() << std::endl; }
}) ;
std::thread t2 ([&] {
try { manager.safe_operation(); }
catch (const std::exception& e) { std::cerr << "线程 2 错误:" << e.what() << std::endl; }
}) ;
t1. join ();
t2. join ();
}
18. 请解释 std::uncaught_exceptions() 与 std::uncaught_exception() 的区别,并说明在析构函数中如何使用它们来判断异常退出状态。【字节跳动 - 基础架构】
std::uncaught_exception():C++98 引入,返回 bool,表示是否有未处理异常
std::uncaught_exceptions():C++17 引入,返回 int,表示当前未处理异常的数量
class SmartResource {
public :
~SmartResource () noexcept {
const int uncaught_count = std::uncaught_exceptions ();
const bool normal_exit = (uncaught_count == 0 );
try {
if (normal_exit) {
complete_cleanup ();
} else {
minimal_cleanup ();
}
} catch (...) {
std::cerr << "清理操作失败" << std::endl;
}
}
private :
void complete_cleanup () { std::cout << "执行完整资源清理" << std::endl; }
void minimal_cleanup () noexcept { std::cout << "执行最小安全清理" << std::endl; }
};
void exception_aware_demo () {
try {
SmartResource resource;
throw std::runtime_error ("测试异常" );
} catch (const std::exception& e) {
std::cout << "捕获异常:" << e.what () << std::endl;
}
}
使用 std::uncaught_exceptions()(C++17+)获取精确的异常数量
在析构函数中根据异常状态选择不同的清理策略
正常退出时执行完整清理,异常退出时执行最小安全清理
所有清理操作都要在 try-catch 块中,确保不会抛出异常
19. 请解释 C++ 函数重载决议的优先级顺序,并说明在什么情况下会出现重载决议歧义。【腾讯 - 微信后台】
精确匹配:参数类型完全一致
类型提升:char→int, float→double 等
标准转换:int→double, 派生类→基类等
用户定义转换:通过转换构造函数或转换运算符
可变参数:最差匹配
void ambiguous (int a, double b) {}
void ambiguous (double a, int b) {}
void test () {
ambiguous (1 , 2 );
}
class ConversionAmbiguity {
public :
operator int () const { return 42 ; }
operator double () const { return 3.14 ; }
};
void process (int x) {}
void process (double x) {}
void test2 () {
ConversionAmbiguity obj;
process (obj);
}
显式类型转换:ambiguous(1, static_cast(2))
使用 static_cast 选择转换路径
重新设计函数签名避免歧义
20. 请解释模板实例化的过程,以及显式实例化和隐式实例化的区别。【阿里巴巴 - 中间件】
语法检查:检查模板语法正确性
参数推导:根据调用推导模板参数
生成代码:用具体类型替换模板参数生成代码
编译优化:对生成的代码进行优化
template <typename T> class DataProcessor {
public :
void process (T data) { std::cout << "Processing: " << data << std::endl; }
};
extern template class DataProcessor <int >;
extern template class DataProcessor <double >;
template class DataProcessor <int >;
template class DataProcessor <double >;
void usage_example () {
DataProcessor<int > processor1;
DataProcessor<double > processor2;
DataProcessor<std::string> processor3;
}
隐式实例化:编译器根据需要自动实例化,可能导致代码膨胀
显式实例化:程序员明确指定实例化,减少编译时间,控制代码生成
21. 请解释 C++ 中的左值、右值和将亡值的区别,并举例说明如何判断一个表达式的值类别。【腾讯 - 微信后台】
左值:有标识符、可取地址、持久存在的表达式示例:变量名、字符串字面量、返回左值引用的函数调用
右值:临时对象、字面量(字符串字面量除外)、返回非引用类型的函数调用
将亡值:有标识符但即将被移动的表达式,是右值的子集
int x = 42 ;
&x;
x = 100 ;
std::move (x);
void func (int &) ;
void func (int &&) ;
int y = 10 ;
func (y);
func (10 );
22. 请解释为什么移动构造函数和移动赋值运算符需要标记为 noexcept,并说明如果没有标记会有什么后果。【阿里巴巴 - 中间件】
标准库优化:std::vector、std::deque 等容器在重新分配内存时,如果移动操作是 noexcept 的,会使用移动而不是拷贝
异常安全:移动操作通常不应该失败,标记 noexcept 提供编译期保证
性能保证:避免移动操作中的异常检查开销
std::vector<MyClass> vec;
vec.push_back (MyClass ());
class MyClass {
public :
MyClass (MyClass&& other) noexcept : data_ (std::move (other.data_)) {
other.data_ = nullptr ;
}
MyClass& operator =(MyClass&& other) noexcept {
if (this != &other) {
delete [] data_;
data_ = other.data_;
other.data_ = nullptr ;
}
return *this ;
}
private :
char * data_;
};
23. 请手写一个简化版的 std::move 实现,并解释其工作原理。【字节跳动 - 基础架构】 #include <type_traits>
template <typename T>
constexpr typename std::remove_reference<T>::type&& my_move (T&& arg) noexcept {
return static_cast <typename std::remove_reference<T>::type&&>(arg);
}
void my_move_demo () {
int x = 42 ;
const int cx = 100 ;
int && r1 = my_move (x);
int && r2 = my_move (123 );
const int && r3 = my_move (cx);
std::cout << "x: " << x << std::endl;
std::cout << "r1: " << r1 << std::endl;
}
模板参数推导:T&&是万能引用,根据传入参数推导类型
移除引用:std::remove_reference移除所有引用修饰
添加右值引用:type&&确保返回右值引用类型
静态转换:static_cast 进行安全的类型转换
24. 请设计一个测试用例,展示移动语义在 std::vector 中的性能优势,并解释为什么移动语义能够提升性能。【美团 - 基础架构】 #include <vector>
#include <chrono>
#include <iostream>
class LargeObject {
public :
LargeObject () : data_ (new int [1000 ]) {
for (int i = 0 ; i < 1000 ; ++i) {
data_[i] = i;
}
}
LargeObject (LargeObject&& other) noexcept : data_ (other.data_) {
other.data_ = nullptr ;
}
~LargeObject () { delete [] data_; }
LargeObject (const LargeObject&) = delete ;
LargeObject& operator =(const LargeObject&) = delete ;
private :
int * data_;
};
void vector_move_performance_test () {
const int iterations = 10000 ;
auto start_move = std::chrono::high_resolution_clock::now ();
std::vector<LargeObject> move_vec;
move_vec.reserve (iterations);
for (int i = 0 ; i < iterations; ++i) {
LargeObject obj;
move_vec.push_back (std::move (obj));
}
auto end_move = std::chrono::high_resolution_clock::now ();
auto move_time = std::chrono::duration_cast <std::chrono::milliseconds>(end_move - start_move);
std::cout << "移动语义耗时:" << move_time.count () << "ms" << std::endl;
std::cout << "vector 最终大小:" << move_vec.size () << std::endl;
}
避免深拷贝:移动操作只转移指针,不复制数据
减少内存分配:避免重复的内存分配和释放
优化容器操作:std::vector 在扩容时使用移动而不是拷贝
更好的缓存局部性:减少内存操作,提高缓存命中率
25. 请解释 std::unique_ptr 如何实现独占所有权,并说明为什么它比 std::auto_ptr 更安全。【腾讯 - 微信后台】
删除拷贝操作:拷贝构造函数和拷贝赋值运算符被标记为= delete
支持移动语义:通过移动构造函数和移动赋值运算符转移所有权
明确所有权转移:必须显式使用 std::move 进行所有权转移
std::auto_ptr<int > ap1 (new int (42 )) ;
std::auto_ptr<int > ap2 = ap1;
std::unique_ptr<int > up1 (new int (42 )) ;
std::unique_ptr<int > up2 = std::move (up1);
安全特性:
编译期检查:拷贝操作在编译期被禁止
显式所有权转移:必须使用 std::move 明确意图
更好的兼容性:支持数组类型和定制删除器
更清晰的语义:明确表示独占所有权
26. 请解释 std::enable_shared_from_this 的工作原理,并说明在什么情况下使用它。【字节跳动 - 基础架构】
内部 weak_ptr:enable_shared_from_this 在基类中存储一个 weak_ptr 指向当前对象
shared_ptr 关联:当通过 std::shared_ptr 构造对象时,会设置内部的 weak_ptr
安全转换:shared_from_this() 通过 weak_ptr::lock() 获取对应的 shared_ptr
异步操作:在回调函数中保持对象存活
成员函数中需要 shared_ptr:当成员函数需要传递 shared_ptr 时
链式调用:返回 shared_ptr 支持链式调用
事件处理:在事件处理器中安全地引用自身
class CorrectUsage : public std::enable_shared_from_this<CorrectUsage> {
public :
static std::shared_ptr<CorrectUsage> create () {
return std::make_shared <CorrectUsage>();
}
void safe_method () {
auto self = shared_from_this ();
}
};
auto obj = CorrectUsage::create ();
obj->safe_method ();
class WrongUsage : public std::enable_shared_from_this<WrongUsage> {
public :
WrongUsage () {
}
};
WrongUsage stack_obj;
27. 请分析 std::shared_ptr 在不同场景下的线程安全性,并给出多线程环境下使用智能指针的最佳实践。【美团 - 基础架构】
控制块线程安全:引用计数操作是原子的,多个线程可以安全地拷贝/析构同一个 shared_ptr
对象访问非线程安全:访问 shared_ptr 指向的对象需要额外的同步机制
同一 shared_ptr 实例非线程安全:对同一个 shared_ptr 实例的写操作需要同步
void safe_access (const std::shared_ptr<Data>& shared_data) {
auto local_copy = shared_data;
std::lock_guard<std::mutex> lock (local_copy->mutex) ;
local_copy->process ();
}
#include <atomic>
std::atomic<std::shared_ptr<Data>> atomic_data;
void atomic_ops () {
auto current = atomic_data.load ();
std::shared_ptr<Data> new_data;
do {
new_data = std::make_shared <Data>(*current);
new_data->modify ();
} while (!atomic_data.compare_exchange_weak (current, new_data));
}
void efficient_design () {
auto unique_data = std::make_unique <Data>();
process_data (*unique_data);
}
减少 shared_ptr 拷贝:在性能关键路径避免不必要的 shared_ptr 拷贝
使用 unique_ptr:当不需要共享所有权时,使用 unique_ptr 减少开销
避免锁竞争:使用细粒度锁或无锁数据结构
28. 请解释 Lambda 表达式按值捕获和按引用捕获的区别,并说明在什么情况下会出现悬空引用问题。【腾讯 - 微信后台】 void capture_comparison () {
int x = 42 ;
std::string str = "Hello" ;
auto value_lambda = [x, str] {
std::cout << "值捕获:" << x << ", " << str << std::endl;
};
auto ref_lambda = [&x, &str] {
std::cout << "引用捕获:" << x << ", " << str << std::endl;
x = 100 ;
str = "Modified" ;
};
value_lambda ();
ref_lambda ();
std::cout << "修改后:x=" << x << ", str=" << str << std::endl;
}
std::function<void () > create_dangling_reference () {
int local_var = 42 ;
return [&local_var] {
std::cout << "捕获的值:" << local_var << std::endl;
};
}
void dangling_reference_demo () {
auto func = create_dangling_reference ();
func ();
}
std::function<void () > create_safe_capture () {
int local_var = 42 ;
return [local_var] {
std::cout << "安全值:" << local_var << std::endl;
};
}
优先使用值捕获:避免悬空引用
小心引用捕获:确保被捕获变量的生命周期长于 Lambda
使用智能指针:共享所有权避免生命周期问题
移动捕获:对于大型对象使用移动语义
29. 解释 std::function 的类型擦除实现原理,并分析其性能开销主要来自哪些方面。【阿里巴巴 - 中间件】
多态基类:使用虚函数和继承实现运行时多态
模板包装器:为每种可调用对象类型生成具体的派生类
动态分配:通常在堆上分配存储空间
统一接口:通过 operator() 提供统一的调用接口
void performance_overhead () {
std::function<int (int )> func = [](int x) { return x * x; };
}
避免频繁创建:重用 std::function 对象
使用模板参数:在性能关键路径使用模板而不是 std::function
小型对象优化:利用 std::function 的小型缓冲区优化
选择适当容器:根据需求选择 std::function 或其他机制
30. 请比较 std::bind 和 Lambda 表达式的优缺点,并说明在现代 C++ 中为什么推荐使用 Lambda 表达式。【百度 - 智能云】
可读性差:std::placeholders::_1 等符号难以理解
调试困难:编译器错误信息复杂
性能开销:多层包装导致间接调用
灵活性有限:难以处理复杂的参数变换
auto lambda = [](int x, int y) { return x * y; };
auto modern_lambda = [value = compute_value ()]() mutable { return value.process (); };
简单参数绑定:使用值捕获或引用捕获
状态保持:Lambda 可以捕获局部变量
复杂逻辑:直接在 Lambda 体中编写逻辑
性能关键路径:避免 std::bind 的开销
接口兼容:需要与期望 std::function 的旧代码交互
复杂参数重排:需要大量参数重新排序时
成员函数绑定:绑定到特定对象实例
31. 请使用 Lambda 表达式实现一个简单的回调机制,要求支持优先级和条件过滤,并保证线程安全。【京东 - 基础架构】 #include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
#include <mutex>
class ThreadSafeCallbackSystem {
public :
using Callback = std::function<void (int )>;
struct PrioritizedCallback {
int priority;
Callback callback;
std::function<bool (int )> filter;
bool operator <(const PrioritizedCallback& other) const {
return priority > other.priority;
}
bool should_execute (int value) const {
return !filter || filter (value);
}
};
void register_callback (int priority, Callback cb, std::function<bool (int )> filter = nullptr ) {
std::lock_guard<std::mutex> lock (mutex_) ;
callbacks_.push_back ({priority, std::move (cb), std::move (filter)});
std::sort (callbacks_.begin (), callbacks_.end ());
}
void trigger_event (int value) {
std::vector<PrioritizedCallback> local_copy;
{
std::lock_guard<std::mutex> lock (mutex_) ;
local_copy = callbacks_;
}
for (const auto & entry : local_copy) {
if (entry.should_execute (value)) {
entry.callback (value);
}
}
}
void clear_callbacks () {
std::lock_guard<std::mutex> lock (mutex_) ;
callbacks_.clear ();
}
size_t callback_count () const {
std::lock_guard<std::mutex> lock (mutex_) ;
return callbacks_.size ();
}
private :
mutable std::mutex mutex_;
std::vector<PrioritizedCallback> callbacks_;
};
void thread_safe_callback_demo () {
ThreadSafeCallbackSystem system;
system.register_callback (1 , [](int value) {
std::cout << "条件回调:" << value << std::endl;
}, [](int value) { return value % 2 == 0 ; });
system.register_callback (3 , [](int value) {
std::cout << "高优先级:" << value << std::endl;
});
system.trigger_event (10 );
system.trigger_event (15 );
std::vector<std::thread> threads;
for (int i = 0 ; i < 10 ; ++i) {
threads.emplace_back ([&system, i] {
system.register_callback (i % 3 + 1 , [i](int value) {
std::cout << "线程" << i << "处理:" << value << std::endl;
});
});
}
for (auto & t : threads) {
t.join ();
}
system.trigger_event (100 );
}
32. 请解释 vector 的扩容机制,并说明为什么通常采用 2 倍扩容策略而不是固定大小扩容。【腾讯 - 后台开发】
指数级增长:当容量不足时,分配新的更大内存块(通常是当前容量的 2 倍)
元素迁移:将原有元素复制到新内存
释放旧内存:删除原有内存空间
void amortized_analysis () {
std::vector<int > vec;
size_t total_copy_operations = 0 ;
for (int i = 0 ; i < 1000000 ; ++i) {
if (vec.size () == vec.capacity ()) {
total_copy_operations += vec.size ();
}
vec.push_back (i);
}
std::cout << "总复制操作次数:" << total_copy_operations << std::endl;
std::cout << "平均每次插入的复制成本:" << static_cast <double >(total_copy_operations) / vec.size () << std::endl;
}
2 倍扩容:均摊时间复杂度为 O(1)
固定大小扩容:均摊时间复杂度为 O(n)
内存利用率:2 倍扩容在时间和空间之间取得良好平衡
33. 请解释哈希表的冲突解决方法,并比较链地址法和开放地址法的优缺点。【阿里巴巴 - 中间件】 template <typename K, typename V>
class ChainingHashTable {
private :
struct Node {
K key;
V value;
Node* next;
};
std::vector<Node*> buckets;
size_t size = 0 ;
size_t hash (const K& key) const {
return std::hash<K>{}(key) % buckets.size ();
}
public :
void insert (const K& key, const V& value) {
size_t index = hash (key);
Node* current = buckets[index];
while (current) {
if (current->key == key) {
current->value = value;
return ;
}
current = current->next;
}
Node* new_node = new Node{key, value, buckets[index]};
buckets[index] = new_node;
size++;
}
};
比较分析:
34. 请使用 STL 容器和算法实现一个外卖订单管理系统,要求支持以下功能:【美团 - 外卖业务】
按餐厅分组统计订单数量
找出每个餐厅的最高金额订单
按时间排序并计算平均配送时间
使用现代 C++ 特性优化性能
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <string>
#include <map>
#include <chrono>
struct Order {
int order_id;
std::string restaurant;
double amount;
std::chrono::system_clock::time_point order_time;
std::chrono::system_clock::time_point delivery_time;
double delivery_duration () const {
return std::chrono::duration_cast <std::chrono::minutes>(delivery_time - order_time).count ();
}
};
class OrderManager {
private :
std::vector<Order> orders;
public :
void add_order (const Order& order) {
orders.push_back (order);
}
std::map<std::string, int > orders_per_restaurant () const {
std::map<std::string, int > result;
for (const auto & order : orders) {
result[order.restaurant]++;
}
return result;
}
std::map<std::string, Order> max_order_per_restaurant () const {
std::map<std::string, Order> result;
for (const auto & order : orders) {
auto it = result.find (order.restaurant);
if (it == result.end () || order.amount > it->second.amount) {
result[order.restaurant] = order;
}
}
return result;
}
void analyze_delivery_times () {
std::sort (orders.begin (), orders.end (), [](const Order& a, const Order& b) {
return a.order_time < b.order_time;
});
double total_time = std::accumulate (orders.begin (), orders.end (), 0.0 , [](double sum, const Order& order) {
return sum + order.delivery_duration ();
});
double avg_time = total_time / orders.size ();
std::cout << "平均配送时间:" << avg_time << "分钟" << std::endl;
for (const auto & order : orders) {
auto duration = order.delivery_duration ();
std::cout << "订单" << order.order_id << ": " << duration << "分钟" << std::endl;
}
}
void optimize_performance () {
orders.reserve (1000 );
Order new_order{1001 , "Restaurant_A" , 45.0 , std::chrono::system_clock::now (), std::chrono::system_clock::now () + std::chrono::minutes (30 )};
orders.emplace_back (std::move (new_order));
auto expensive_orders = std::count_if (orders.begin (), orders.end (), [](const Order& o) {
return o.amount > 50.0 ;
});
std::cout << "高金额订单数量:" << expensive_orders << std::endl;
}
};
void order_management_demo () {
OrderManager manager;
auto now = std::chrono::system_clock::now ();
manager.add_order ({1 , "Restaurant_A" , 35.0 , now, now + std::chrono::minutes (25 )});
manager.add_order ({2 , "Restaurant_B" , 55.0 , now, now + std::chrono::minutes (40 )});
manager.add_order ({3 , "Restaurant_A" , 42.0 , now, now + std::chrono::minutes (30 )});
manager.add_order ({4 , "Restaurant_C" , 28.0 , now, now + std::chrono::minutes (20 )});
manager.add_order ({5 , "Restaurant_B" , 60.0 , now, now + std::chrono::minutes (35 )});
auto restaurant_counts = manager.orders_per_restaurant ();
std::cout << "各餐厅订单数量:" << std::endl;
for (const auto & [restaurant, count] : restaurant_counts) {
std::cout << restaurant << ": " << count << std::endl;
}
auto max_orders = manager.max_order_per_restaurant ();
std::cout << "\n各餐厅最高金额订单:" << std::endl;
for (const auto & [restaurant, order] : max_orders) {
std::cout << restaurant << ": 订单#" << order.order_id << ", 金额:$" << order.amount << std::endl;
}
std::cout << "\n配送时间分析:" << std::endl;
manager.analyze_delivery_times ();
std::cout << "\n性能优化:" << std::endl;
manager.optimize_performance ();
}
35. 实现一个基于 STL 的推荐算法,要求对用户行为数据进行以下处理:【字节跳动 - 推荐系统】
使用 map/reduce 模式统计用户行为
使用自定义排序算法对推荐结果排序
使用移动语义优化大数据传输
保证线程安全
#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
#include <numeric>
#include <thread>
#include <mutex>
struct UserBehavior {
int user_id;
int item_id;
double rating;
std::chrono::system_clock::time_point timestamp;
};
class RecommendationSystem {
private :
std::vector<UserBehavior> behaviors;
mutable std::mutex mtx;
public :
void add_behavior (UserBehavior&& behavior) {
std::lock_guard<std::mutex> lock (mtx) ;
behaviors.emplace_back (std::move (behavior));
}
std::map<int , double > calculate_item_ratings () const {
std::map<int , double > item_ratings;
std::map<int , int > item_counts;
for (const auto & behavior : behaviors) {
item_ratings[behavior.item_id] += behavior.rating;
item_counts[behavior.item_id]++;
}
for (auto & [item_id, total] : item_ratings) {
total /= item_counts[item_id];
}
return item_ratings;
}
std::vector<std::pair<int , double >> get_recommendations () const {
auto item_ratings = calculate_item_ratings ();
std::vector<std::pair<int , double >> recommendations;
recommendations.reserve (item_ratings.size ());
for (const auto & [item_id, rating] : item_ratings) {
recommendations.emplace_back (item_id, rating);
}
std::sort (recommendations.begin (), recommendations.end (), [](const auto & a, const auto & b) {
return a.second > b.second;
});
return recommendations;
}
void process_in_parallel () {
auto recommendations = get_recommendations ();
std::vector<std::thread> threads;
const size_t num_threads = std::thread::hardware_concurrency ();
const size_t chunk_size = recommendations.size () / num_threads;
for (size_t i = 0 ; i < num_threads; ++i) {
size_t start = i * chunk_size;
size_t end = (i == num_threads - 1 ) ? recommendations.size () : start + chunk_size;
threads.emplace_back ([&, start, end] {
for (size_t j = start; j < end; ++j) {
std::lock_guard<std::mutex> lock (mtx);
std::cout << "处理推荐项目:" << recommendations[j].first << ", 评分:" << recommendations[j].second << std::endl;
}
});
}
for (auto & thread : threads) {
thread.join ();
}
}
};
void recommendation_demo () {
RecommendationSystem system;
auto now = std::chrono::system_clock::now ();
system.add_behavior ({1 , 101 , 4.5 , now});
system.add_behavior ({1 , 102 , 3.8 , now});
system.add_behavior ({2 , 101 , 4.2 , now});
system.add_behavior ({2 , 103 , 4.7 , now});
system.add_behavior ({3 , 102 , 4.1 , now});
system.add_behavior ({3 , 104 , 4.9 , now});
auto recommendations = system.get_recommendations ();
std::cout << "推荐结果排序:" << std::endl;
for (const auto & [item_id, rating] : recommendations) {
std::cout << "项目" << item_id << ": " << rating << "分" << std::endl;
}
std::cout << "\n并行处理推荐结果:" << std::endl;
system.process_in_parallel ();
}
36. 请使用 SFINAE 技术实现一个函数重载,仅当类型 T 具有 size() 方法时才调用特定版本。【腾讯 - 微信后台】 #include <iostream>
#include <type_traits>
template <typename T, typename = void >
struct has_size_method : std::false_type {};
template <typename T>
struct has_size_method <T, std::void_t <decltype (std::declval <T>().size ())>> : std::true_type {};
template <typename T>
auto process_container (T&& container) -> std::enable_if_t <has_size_method<T>::value, void > {
std::cout << "容器大小:" << container.size () << std::endl;
}
template <typename T>
auto process_container (T&& container) -> std::enable_if_t <!has_size_method<T>::value, void > {
std::cout << "该类型没有 size() 方法" << std::endl;
}
class WithSize {
public :
size_t size () const { return 42 ; }
};
class WithoutSize {};
void sfinae_interview_demo () {
std::vector<int > vec = {1 , 2 , 3 };
WithSize ws;
WithoutSize wos;
process_container (vec);
process_container (ws);
process_container (wos);
process_container (100 );
}
37. 请使用变参模板实现一个 compile-time 的字符串拼接功能,要求支持不同类型参数的拼接。【阿里巴巴 - 中间件】 #include <iostream>
#include <string>
#include <sstream>
template <typename ... Args>
std::string concat (Args&&... args) {
std::ostringstream oss;
(oss << ... << std::forward<Args>(args));
return oss.str ();
}
template <typename ... Args>
constexpr size_t concatenated_length (Args&&... args) {
return (0 + ... + std::string_view (args).size ());
}
void string_concat_demo () {
std::cout << "=== 编译期字符串拼接 ===" << std::endl;
auto result = concat ("Hello" , " " , "World" , " " , 2025 , "!" , 3.14 );
std::cout << "拼接结果:" << result << std::endl;
constexpr size_t len = concatenated_length ("Hello" , " " , "World" );
std::cout << "预计长度:" << len << std::endl;
std::cout << concat ("整数:" , 42 , ", 浮点数:" , 3.14 , ", 布尔:" , true ) << std::endl;
}
38. 请实现一个类型特征,用于检测类是否具有特定的成员函数,并基于此实现一个策略类。【美团 - 平台技术】 #include <iostream>
#include <type_traits>
template <typename T, typename = void >
struct has_serialize : std::false_type {};
template <typename T>
struct has_serialize <T, std::void_t <decltype (std::declval <T>().serialize (std::declval <std::ostream&>()))>> : std::true_type {};
template <typename T>
constexpr bool has_serialize_v = has_serialize<T>::value;
template <typename T>
class SerializationStrategy {
public :
void serialize (const T& obj, std::ostream& os) {
if constexpr (has_serialize_v<T>) {
obj.serialize (os);
} else {
default_serialize (obj, os);
}
}
private :
void default_serialize (const T& obj, std::ostream& os) {
os << "Default serialization for " << typeid (T).name ();
}
};
class CustomSerializable {
public :
void serialize (std::ostream& os) const {
os << "Custom serialization: value=" << value;
}
int value = 42 ;
};
class NotSerializable {
public :
int data = 100 ;
};
void serialization_demo () {
std::cout << "=== 序列化策略实现 ===" << std::endl;
SerializationStrategy<CustomSerializable> strategy1;
SerializationStrategy<NotSerializable> strategy2;
CustomSerializable obj1;
NotSerializable obj2;
std::cout << "可序列化类:" ;
strategy1. serialize (obj1, std::cout);
std::cout << std::endl;
std::cout << "不可序列化类:" ;
strategy2. serialize (obj2, std::cout);
std::cout << std::endl;
}
39. 请使用 C++20 概念 (Concepts) 重新实现一个类型安全的数学库,支持不同类型的算术运算。【百度 - 搜索架构】 #include <iostream>
#include <concepts>
#include <vector>
#include <cmath>
template <typename T>
concept FloatingPoint = std::floating_point<T>;
template <typename T>
concept Integral = std::integral<T>;
template <typename T>
concept Number = std::integral<T> || std::floating_point<T>;
template <typename T>
concept ComplexNumber = requires (T a) {
{ a.real () } -> Number;
{ a.imag () } -> Number;
};
template <Number T>
T add (T a, T b) {
return a + b;
}
template <Number T>
T multiply (T a, T b) {
return a * b;
}
template <FloatingPoint T>
T sqrt (T value) {
return std::sqrt (value);
}
template <Integral T>
double sqrt (T value) {
return std::sqrt (static_cast <double >(value));
}
template <ComplexNumber T>
auto magnitude (const T& complex) -> decltype (complex.real()) {
return std::sqrt (complex.real () * complex.real () + complex.imag () * complex.imag ());
}
template <Number T>
class Vector {
public :
Vector (std::initializer_list<T> init) : data_ (init) {}
template <Number U>
auto dot (const Vector<U>& other) const {
using ResultType = decltype (std::declval <T>() * std::declval <U>());
ResultType result = 0 ;
for (size_t i = 0 ; i < data_.size (); ++i) {
result += data_[i] * other.data_[i];
}
return result;
}
private :
std::vector<T> data_;
};
void math_library_demo () {
std::cout << "=== 类型安全数学库 ===" << std::endl;
std::cout << "加法:" << add (5 , 3 ) << std::endl;
std::cout << "乘法:" << multiply (2.5 , 4.0 ) << std::endl;
std::cout << "浮点平方根:" << sqrt (16.0 ) << std::endl;
std::cout << "整数平方根:" << sqrt (25 ) << std::endl;
struct Complex {
double real () const { return r; }
double imag () const { return i; }
double r, i;
};
Complex c{3.0 , 4.0 };
std::cout << "复数模长:" << magnitude (c) << std::endl;
Vector<int > v1 = {1 , 2 , 3 };
Vector<double > v2 = {4.0 , 5.0 , 6.0 };
std::cout << "向量点积:" << v1. dot (v2) << std::endl;
}
40. 请解释 std::thread 的 detach() 和 join() 方法的区别,并说明在什么情况下应该使用 detach。【腾讯 - 后台开发】 void detach_vs_join () {
std::thread t ([] { std::this_thread::sleep_for(std::chrono::seconds(1 )); std::cout << "后台线程完成" << std::endl; }) ;
t.detach ();
std::cout << "主线程继续执行" << std::endl;
std::this_thread::sleep_for (std::chrono::seconds (2 ));
}
优先使用 join():确保线程正确完成,避免资源泄漏
谨慎使用 detach():仅在确实需要后台运行且不关心结果时使用
detach 适用场景:后台日志记录监控和心跳线程不关心执行结果的清理任务
void detach_risks () {
std::string local_data = "important" ;
std::thread risky_thread ([&local_data] {
std::this_thread::sleep_for(std::chrono::seconds(1 ));
std::cout << local_data << std::endl;
}) ;
risky_thread.detach ();
}
41. 请解释 std::async 的异常传播机制,并说明在异步任务中抛出异常会发生什么。【阿里巴巴 - 中间件】 void async_exception_mechanism () {
auto throwing_task = []() -> int {
std::cout << "异步任务开始" << std::endl;
throw std::runtime_error ("异步任务发生错误" );
return 42 ;
};
try {
std::future<int > future = std::async (std::launch::async, throwing_task);
std::this_thread::sleep_for (std::chrono::milliseconds (100 ));
std::cout << "主线程继续执行..." << std::endl;
int result = future.get ();
std::cout << "结果:" << result << std::endl;
} catch (const std::exception& e) {
std::cout << "在主线程捕获异常:" << e.what () << std::endl;
}
}
异常捕获:async 捕获任务抛出的所有异常
延迟抛出:异常在调用 future.get() 时重新抛出
异常类型保持:异常类型和消息保持不变
线程安全:异常传播是线程安全的
void async_exception_best_practice () {
auto safe_async_call = []() {
try {
std::future<int > future = std::async (std::launch::async, [] { return risky_computation (); });
int result = future.get ();
process_result (result);
} catch (const std::exception& e) {
std::cerr << "异步操作失败:" << e.what () << std::endl;
}
};
}
42. 请解释什么是顺序一致性?memory_order_relaxed 适用于什么场景?【腾讯 - 微信后台】 参考答案:
顺序一致性(Sequential Consistency):
void sequential_consistency_demo () {
std::atomic<int > x (0 ) , y (0 ) ;
std::thread t1 ([&]() {
x.store(1 , std::memory_order_seq_cst);
y.store(1 , std::memory_order_seq_cst);
}) ;
std::thread t2 ([&]() {
int r1 = y.load(std::memory_order_seq_cst);
int r2 = x.load(std::memory_order_seq_cst);
std::cout << "r1=" << r1 << ", r2=" << r2 << std::endl;
}) ;
t1. join ();
t2. join ();
}
全局顺序:所有线程看到相同的操作顺序
即时可见:写操作对所有线程立即可见
最强保证:最简单的正确性模型,但性能开销最大
memory_order_relaxed 适用场景:
void relaxed_appropriate_use () {
std::atomic<int > counter (0 ) ;
std::vector<std::thread> threads;
for (int i = 0 ; i < 10 ; ++i) {
threads.emplace_back ([&counter]() {
for (int j = 0 ; j < 1000 ; ++j) {
counter.fetch_add (1 , std::memory_order_relaxed);
}
});
}
for (auto & t : threads) t.join ();
std::cout << "最终计数:" << counter.load () << std::endl;
std::atomic<bool > shutdown_flag (false ) ;
std::thread worker ([&shutdown_flag]() {
while (!shutdown_flag.load(std::memory_order_relaxed)) {
}
}) ;
shutdown_flag.store (true , std::memory_order_relaxed);
worker.join ();
}
relaxed 使用场景:
原子计数器:只需要原子性,不需要顺序保证
状态标志:简单的布尔标志,无数据依赖
性能关键路径:对性能要求极高的场景
无数据竞争:确保没有其他数据依赖关系
43. 请解释 C++17 结构化绑定的工作原理,并说明它在什么场景下比传统方式更有优势。【腾讯 - 微信后台】 void structured_binding_mechanism () {
std::pair<int , std::string> data{42 , "answer" };
auto & [num, str] = data;
std::cout << num << ": " << str << std::endl;
}
多返回值处理:函数返回 tuple/pair 时直接解包
容器遍历:特别是 map 的 key-value 遍历
数据解包:复杂数据结构的字段访问
代码简洁:减少中间变量,提高可读性
void traditional_vs_modern () {
std::map<std::string, int > scores = {{"Alice" , 95 }, {"Bob" , 88 }};
for (const auto & pair : scores) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
for (const auto & [name, score] : scores) {
std::cout << name << ": " << score << std::endl;
}
}
44. 请详细说明 std::string_view 的生命周期问题,并给出安全使用的最佳实践。【字节跳动 - 基础架构】 void lifecycle_examples () {
std::string_view dangerous1 = std::string ("临时字符串" );
auto create_view = []() -> std::string_view {
std::string local = "局部变量" ;
return local;
};
std::string_view dangerous2 = create_view ();
std::string* dynamic_str = new std::string ("动态字符串" );
std::string_view view (*dynamic_str) ;
delete dynamic_str;
}
void safe_practices () {
std::string_view safe1 = "字符串字面量" ;
std::string persistent = "持久字符串" ;
std::string_view safe2 = persistent;
auto process_safely = [](std::string_view sv) {
std::cout << "处理:" << sv.substr (0 , std::min (sv.size (), size_t (10 ))) << std::endl;
};
class SafeContainer {
public :
void add_string (std::string str) {
strings_.push_back (std::move (str));
}
std::string_view get_view (size_t index) const {
return strings_.at (index);
}
private :
std::vector<std::string> strings_;
};
}
设计原则:
不拥有原则:string_view 不管理内存,只提供视图
生命周期保证:确保原字符串比视图生命周期长
明确契约:在 API 文档中明确生命周期要求
谨慎返回:避免从函数返回可能悬空的 string_view
45. 请说明 C++20 范围库中视图 (View) 与容器 (Container) 的主要区别,并解释惰性求值的优势。【阿里巴巴 - 中间件】 void view_vs_container () {
std::vector<int > container = {1 , 2 , 3 , 4 , 5 };
auto view = container | std::views::filter ([](int n) { return n % 2 == 0 ; });
std::cout << "容器大小:" << container.size () << std::endl;
std::cout << "视图大小:" << std::ranges::size (view) << std::endl;
container.push_back (6 );
std::cout << "修改后视图大小:" << std::ranges::size (view) << std::endl;
}
数据所有权:容器拥有数据,视图仅引用数据
内存分配:容器需要分配内存,视图不需要
修改影响:修改容器会影响视图,视图操作不影响容器
生命周期:视图依赖原容器生命周期
void lazy_evaluation_advantages () {
std::vector<int > large_data (1000000 ) ;
std::iota (large_data.begin (), large_data.end (), 0 );
auto lazy_result = large_data | std::views::filter ([](int n) { return n % 2 == 0 ; }) | std::views::transform ([](int n) { return n * n; }) | std::views::take (10 );
std::cout << "惰性求值结果:" ;
for (int n : lazy_result) std::cout << n << " " ;
std::cout << std::endl;
std::vector<int > eager_result;
for (int n : large_data) {
if (n % 2 == 0 ) {
eager_result.push_back (n * n);
if (eager_result.size () == 10 ) break ;
}
}
}
性能优化:只计算需要的元素
内存效率:避免中间结果存储
无限序列:支持处理无限序列
组合性:易于组合多个操作
46. 请解释 C++20 协程的底层机制,包括协程句柄、承诺类型和等待器的角色。【字节跳动 - 基础架构】 struct CoroutineHandleDemo {
std::coroutine_handle<> handle;
void resume () {
if (handle && !handle.done ()) {
handle.resume ();
}
}
void destroy () {
if (handle) {
handle.destroy ();
}
}
};
struct MyPromise {
int result_value;
std::suspend_always initial_suspend () { return {}; }
std::suspend_always final_suspend () noexcept { return {}; }
void unhandled_exception () { std::terminate (); }
void return_value (int value) { result_value = value; }
MyCoroutine get_return_object () {
return MyCoroutine{std::coroutine_handle<MyPromise>::from_promise (*this )};
}
};
struct MyAwaiter {
bool await_ready () const noexcept { return false ; }
void await_suspend (std::coroutine_handle<>) const noexcept {}
int await_resume () const noexcept { return 42 ; }
};
协程句柄:控制协程生命周期(恢复、销毁)
承诺类型:定义协程行为(初始/最终挂起、返回值处理)
等待器:控制挂起和恢复逻辑(就绪检查、挂起操作、恢复值)
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online