nlohmann/json 的 basic_json::emplace:向 JSON 对象原地构造并安全插入新成员
nlohmann/json 的 basic_json::emplace向 JSON 对象原地构造并安全插入新成员【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/jsonJSON for Modern Cnlohmann/json为basic_json提供了丰富的修改值接口其中emplace()是向JSON 对象按 key 原地构造并插入成员的核心成员函数。本指南以官方 API 文档为骨架结合 json.hpp 源码实现与 unit-modifiers.cpp 单元测试系统讲解emplace()的签名、null 自动转换语义、返回的(iterator, bool)判定规则、type_error.311异常边界与ordered_json下的迭代器失效细节并给出可直接编译运行的完整示例帮助你准确区分它与insert、push_back、emplace_back、operator[]的适用场景。函数签名与核心语义emplace()的完整声明定义于 json.hpp是basic_json的一个成员模板函数templateclass... Args std::pairiterator, bool emplace(Args ... args);它的工作方式与标准库关联容器的emplace完全一致但作用对象是basic_json值本身核心语义为在JSON 对象上用给定的args就地构造in-place construct一个新成员并且仅当容器中不存在相同 key 的元素时才真正插入如果被调用的 JSON 值当前是#!json null会先静默创建一个空对象再向其中追加由args构造出的值若 key 已存在则不发生任何修改args也不会被构造为元素。对底层的std::map默认object_t而言这意味着emplace避免了先构造临时basic_json、再由insert拷贝/移动的中间步骤——构造直接发生在为对象元素分配好的存储空间内。模板参数与普通参数成员含义模板参数Args一组兼容类型用于构造一个basic_json对象例如string_t、数值、bool、数组/对象、嵌套basic_json等argsin转发给某个basic_json构造函数的一组实参通过完美转发perfect forwardingArgs ...向下传递由于args直接参与basic_json的构造函数重载决议标准对象构造规则在这里都适用。例如传入(3, foo)会被解析为构造一个包含 3 个foo的数组值——这正是 emplace 与operator[]/insert在构造时机上的差异所在。返回值(iterator, bool)双重语义与标准库关联容器一致emplace()返回std::pairiterator, boolfirst指向被插入元素的迭代器如果 key 已存在导致未发生插入则指向已存在的那个元素即原值不会被替换second#!cpp bool布尔标志true表示本次确实发生了插入false表示 key 已存在、未做任何修改。官方文档 emplace.md 将此表述为由指向已插入元素或若未插入已存在元素的迭代器与一个表示是否发生插入的布尔值组成的 pair。在默认object_t std::map下判断已存在的依据是对象当前使用的比较器默认字典序比较 key。迭代器失效规则对于普通basic_json默认底层为std::mapemplace()不会使既有迭代器与引用失效。但文档特别强调了一种例外当使用 ordered_json底层为 ordered_map.hpp 中的ordered_map本质是基于 vector 的顺序容器时向对象追加新成员可能触发重分配reallocation此时所有迭代器包括end()迭代器以及所有指向既有元素的引用都会失效。这与 ordered_map.hpp 的实现直接相关ordered_map::emplace先做一次线性查找判断 key 是否已存在再调用底层Container::emplace_back把元素追加到末尾因此一旦容量不足发生扩容迭代器与引用即全部失效。从源码结构看这也是ordered_json相比默认std::map在插入语义上最需要留意的差异——前者保序但存在扩容失效后者保证引用稳定性但按键排序。异常安全与异常类型emplace()提供强异常保证strong guarantee如果构造过程中抛出异常任何 JSON 值都不会发生改变——不会出现对象已从 null 变成 object但成员没有插入之类的半成品状态。type_error.311当在既非 JSON 对象也非#!json null的值如数值、数组、字符串、布尔上调用emplace()时会抛出json::type_error异常标识为311完整定义见 exceptions.md[json.exception.type_error.311] cannot use emplace() with number消息中的number会随实际类型名变化如cannot use emplace() with array。注意文档明确指出emplace()仅接受object 或 null这一点与面向数组的emplace_back()接受 array 或 null正好互补两者的类型检查错误共用 311 编号。对应源码中的守卫逻辑位于 json.hpp// emplace only works for null objects or arrays if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object()))) { JSON_THROW(type_error::create(311, detail::concat(cannot use emplace() with , type_name()), this)); }若通过则当值为 null 时源码会先把内部类型标记与存储区切换为空对象json.hpp// transform a null object into an object if (is_null()) { m_data.m_type value_t::object; m_data.m_value value_t::object; assert_invariant(); }复杂度默认object_tstd::map对数复杂度 O(log(size()))因为底层是红黑树插入与查找均为对数时间。需要再次提醒ordered_json的底层 ordered_map 使用线性查找 末尾追加实际复杂度是 O(n)官方文档给出的 O(log(size())) 针对的是默认关联容器实现。完整示例与输出解读官方示例 emplace.cpp 是学习该 API 的最佳起点它同时演示了 null 自动转对象、重复 key 不覆盖两个关键行为#include iostream #include nlohmann/json.hpp using json nlohmann::json; int main() { // create JSON values json object {{one, 1}, {two, 2}}; json null; // print values std::cout object \n; std::cout null \n; // add values auto res1 object.emplace(three, 3); null.emplace(A, a); null.emplace(B, b); // the following call will not add an object, because there is already // a value stored at key B auto res2 null.emplace(B, c); // print values std::cout object \n; std::cout *res1.first std::boolalpha res1.second \n; std::cout null \n; std::cout *res2.first std::boolalpha res2.second \n; }编译并运行使用单头文件版本即可例如g -stdc11 -I single_include emplace.cpp{one:1,two:2} null {one:1,three:3,two:2} 3 true {A:a,B:b} b false输出可以逐行对照理解前两行是初始状态一个普通对象与一个值为null的 JSON 值object.emplace(three, 3)成功后对象多出three:3返回迭代器指向3second为true两个对null的emplace(A, a)、emplace(B, b)调用把null静默升级成对象{A:a,B:b}——整个过程无需你预先判断或手工初始化最后null.emplace(B, c)因为 keyB已存在而没有覆盖原值*res2.first仍打印b原有元素second打印false。测试用例对行为的验证单元测试 unit-modifiers.cpp 的emplace()小节对上述语义做了逐条断言可作为行为契约来阅读从 null 起步默认构造的json j;依次emplace(foo, bar)、emplace(baz, bam)测试断言res1.second true、j.type() json::value_t::objectnull 已转型再emplace(baz, bad)时断言res3.second false且*res3.first bam原值未被替换最终对象等于{{baz,bam},{foo,bar}}在已有对象上插入json j {{foo, bar}}上emplace(baz, bam)成功second true重复插入foo失败且既有bar保持不变非法类型json j 1;时j.emplace(foo, bar)抛出异常断言消息精确匹配[json.exception.type_error.311] cannot use emplace() with number。与相关接口的分工对照接口适用容器返回值行为要点emplace()object / nullpairiterator, bool按 key 就地构造key 已存在则不插入不覆盖null 先转对象emplace_back()emplace_back.mdarray / nullreference就地构造后追加到数组末尾null 先转数组自 3.7.0 起返回引用摊还常数复杂度insert()insert.mdarray / objectiterator或void把已构造好的值按迭代器位置 / key 范围插入operator[]/push_back()object / array引用 /reference前者在 key 不存在时默认构造空值并返回引用可用于赋值从对象语义看emplace()最接近键存在即放弃绝不自作主张覆盖的原子判空操作适合实现幂等的成员装配逻辑而operator[]则会为缺失的 key 就地创建一个空值通常是null供随后赋值两者存在本质差别。更系统的修改场景梳理可参考特性指南 modifying_values.md。版本历史与兼容性自2.0.8版本起提供emplace()签名与语义保持向后兼容源码实现基于类型检查 null 转型 底层容器转发三步完成见 json.hpp配合set_parent处理基于std::unique_ptr的父指针追踪SAX/串行化子值归属管理。使用建议小结需要不覆盖已有键地向对象写入数据、且希望在插入点就地构造值避免多余临时对象时优先使用emplace()对null值调用是安全的库会自动把它升级为对象但请在逻辑上意识到这种隐式转型读取返回值时务必同时消费second只有它为true时才真正新增了元素遍历或持有ordered_json的迭代器期间反复调用emplace()要考虑底层 vector 扩容导致的整体失效不要期望emplace()能修改已存在键的值——那是operator[]或insert_or_assign类语义的职责。【免费下载链接】jsonJSON for Modern C项目地址: https://gitcode.com/GitHub_Trending/js/json创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考