不能直接用 std::string 存多语言文本,因其本质是 char 序列,对非 ASCII 字符支持脆弱:长度计算错误、截断乱码、排序异常;应统一用 std::u32string 内部存储,对外提供 UTF-8 编码的 std::string 接口。

为什么不能直接用 std::string 存多语言文本
因为 std::string 本质是 char 序列,对中文、日文、阿拉伯文等非 ASCII 字符支持脆弱:长度计算错、截断乱码、排序异常。比如 "你好" 在 UTF-8 下占 6 字节但只有 2 个 Unicode 码点,.size() 返回 6,不是字符数。硬塞进 std::string 后做索引或拼接,很容易越界或丢字。
真正安全的做法是统一用 std::u32string(每个字符 4 字节,对应一个 Unicode 码点)做内部存储,对外提供 UTF-8 编码的 std::string 接口,由管理器负责编解码转换。
如何设计 TranslationManager 的核心接口
关键不是“存多少种语言”,而是“怎么让调用方不感知编码细节”。接口要满足:按语言 ID 获取翻译、支持 fallback(如 en-US → en)、能热重载新语言包。不需要泛型模板,用 std::string 传语言 ID(如 "zh-CN")最实用。
-
void load(const std::string& lang, const std::string& json_path):从 JSON 文件加载键值对,键是英文原文(如"save_button"),值是该语言的翻译文本 -
std::string get(const std::string& key, const std::string& lang = "en") const:返回 UTF-8 编码的字符串;若lang不存在,自动 fallback 到"en" -
void set_fallback(const std::string& fallback_lang):可覆盖默认 fallback 行为
注意:所有 get() 返回前必须确保内部 std::u32string 已转为合法 UTF-8 —— 用 std::wstring_convert<:codecvt_utf8>, char32_t></:codecvt_utf8> 或更现代的 std::from_chars/std::to_chars(C++17 起)配合 std::vector<char32_t></char32_t> 手动转换,避免依赖 locale。
立即学习“C++免费学习笔记(深入)”;
JSON 加载时怎么处理不同编码的源文件
绝大多数编辑器保存 JSON 默认是 UTF-8,但 Windows 上某些旧工具可能输出 GBK 或 Shift-JIS。如果直接用 std::ifstream 读,遇到非法字节序列会静默截断或填充 \0,导致后续解析失败或 key 错位。
实操建议:
- 强制以二进制模式打开:
std::ifstream f(path, std::ios::binary),防止系统自动换行符/编码转换 - 读取后检查前 3 字节是否为 UTF-8 BOM(
0xEF 0xBB 0xBF);若无 BOM,假设为 UTF-8 —— 这是 JSON 规范要求的默认编码 - 用
nlohmann::json解析时传入std::vector<uint8_t></uint8_t>原始字节,它内部会校验 UTF-8 合法性;解析失败时抛出异常并打印具体错误位置,而不是吞掉 - 禁止接受任何带 BOM 的 UTF-16/UTF-32 JSON —— 没有编辑器该这么导出翻译表
多线程环境下 get() 调用怎么避免锁竞争
翻译数据本身是只读的(加载后不修改),但 fallback 查找和字符串转换涉及临时对象构造。如果每次 get() 都加全局互斥锁,高并发下会成瓶颈。
正确做法:
- 把每种语言的数据存在
std::unordered_map<:string std::u32string></:string>中,整个 map 用std::shared_mutex保护(C++17)—— 加载时写锁,查询时共享读锁 -
get()内部不做任何 new/malloc:预先分配好std::vector<char32_t></char32_t>缓冲区(如 512 项),复用转换空间 - fallback 链最多 2 层(如
zh-HK → zh → en),用std::array<:string></:string>存候选语言 ID,避免动态分配 - 绝对不要在
get()里触发磁盘 IO 或网络请求 —— 那是加载阶段的事
最容易被忽略的是:即使用了 std::shared_mutex,也要确保所有语言 map 的生命周期长于任何 worker 线程,否则线程还在读,map 已被析构,就是 UAF。


















