多益网络:游戏引擎研发工程师-C++ 一面凉经
1. 介绍一下你的 RPC 项目?这个项目是实际应用还是练手?
2. 你的 RPC 框架中,应用服务器可以有多台,怎么选择调用哪一个?
这涉及到负载均衡策略。常用的策略包括:
- 轮询(Round-Robin):按顺序依次分配,适合各服务器性能相近的场景。
- 随机(Random):随机选择一台。
- 哈希(一致性 Hash):根据客户端 IP 或请求参数的 Hash 值绑定到固定节点,适合有状态服务或需要缓存命中率的场景。
- 加权轮询 / 最少连接数:根据服务器的实时负载或预设权重进行动态分发。我的 Demo 中主要实现了一致性哈希和单纯的轮询机制。
3. 怎么保证多个应用服务器之间数据的同步呢?
RPC 框架本身是一层通信中间件,它的职责是透明、高效地完成跨网络的方法调用。多个应用服务器之间的数据同步,属于业务应用层或数据存储层该考虑的问题,不应该耦合在 RPC 框架中。如果是无状态服务,根本不需要同步;如果是有状态服务,通常会依赖统一的后端存储(如 Redis、MySQL)或者使用分布式共识算法(如 Raft/ZooKeeper)来保证数据一致性。
4. 你的 RPC 项目采用哪种序列化方式?为什么不用 JSON?
RPC 通信通常优先选用 Protobuf 或自定义的二进制 TLV 协议,而不是 JSON。JSON 是文本协议,冗余字符(如括号、引号)多,体积大,且解析时需要进行大量的字符串匹配和类型转换,十分消耗 CPU 资源。Protobuf 基于二进制,采用 Varint 编码压缩整数,序列化和反序列化速度极快,网络带宽占用极小,非常适合高频的内部 RPC 调用。
5. RPC 调用中如果发生了网络超时,你是怎么处理机制的?
需要在 RPC 客户端实现超时检测与重试机制。利用 Epoll 或定时器(如时间轮),在发出请求时设定一个 Callback 和超时时间。如果规定时间内未收到服务端的 Response,触发超时错误。如果该接口是幂等的(如查询操作),可以根据配置自动重试 2-3 次;如果非幂等(如扣款),则直接向上层抛出异常,由业务侧决定是否发起逆向查询或补偿。
6. C++ 中有左值和右值,它们有什么区别?右值引用有什么用?
- 左值(Lvalue):可以取地址、有名字、非临时的变量(如普通的局部变量)。
- 右值(Rvalue):不能取地址、没有名字、临时的变量(如字面量 10、函数返回的临时对象 getObj())。
- 右值引用(&&)的作用:主要用于实现移动语义(Move Semantics)。以往对临时大对象进行操作会触发低效的深拷贝,而右值引用可以“窃取”临时对象底层的堆内存指针,将深拷贝转化为浅拷贝,极大地提升了性能。
7. std::move 和 std::forward 有什么区别?
- std::move :不移动任何实际内存,它的作用仅仅是将一个左值强制转换为右值引用(static_cast<T&&>),以便触发对象的移动构造函数。
- std::forward :用于模板编程中的完美转发。它结合了引用折叠规则,能够保留参数原本的值类别:如果传入的是左值,它就转发为左值;如果传入的是右值,它就转发为右值,确保传递给内层函数时属性不丢失。
8. C++ 多态底层的虚函数表(vptr 和 vtbl)机制是怎样的?
当类中包含虚函数时,编译器会在该类的内存布局的起始位置安插一个隐式的虚指针(vptr)。这个指针指向一个存储在只读数据段(.rodata)的虚函数表(vtbl),表中存放着该类所有虚函数的直接地址。在运行时,通过基类指针调用派生类的虚函数时,实际上是通过访问该对象实例内部的 vptr,查找到对应派生类的虚函数表,再进行动态决议调用,这也就是动态绑定的底层实现。
9. 编程设计题:在游戏中实现工具类和背包类,支持对工具的增加、删除。
思路: 工具类包含简单的属型。背包类作为一个容器管理器,核心使用 std::unordered_map 来实现 的快速插入和删除。代码实现:
#include <iostream>
#include <string>
#include <unordered_map>
#include <memory>
class Tool {
public:
int id;
std::string name;
Tool(int i, std::string n) : id(i), name(std::move(n)) {}
~Tool() { std::cout << "Tool " << name << " destroyed\n"; }
};
class Backpack {
private:
int capacity;
std::unordered_map<std::string, std::shared_ptr<Tool>> items;
public:
Backpack(int cap) : capacity(cap) {}
bool addTool(const std::shared_ptr<Tool>& tool) {
if (items.size() >= capacity) {
std::cout << "Backpack full!\n";
return false;
}
if (items.find(tool->name) != items.end()) {
std::cout << "Tool already exists!\n";
return false;
}
items[tool->name] = tool;
return true;
}
void removeTool(const std::string& name) {
auto it = items.find(name);
if (it != items.end()) {
items.erase(it);
} else {
std::cout << "Tool not found!\n";
}
}
};
10. 你在背包设计中使用了 unordered_map<string, shared_ptr>,为什么要使用 shared_ptr 智能指针?用 unique_ptr 不行吗?
使用 shared_ptr 是因为在游戏逻辑中,这个工具对象可能不仅仅存
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.

查看13道真题和解析