首页
/ JSON for Modern C++:json_pointer::operator== 相等性比较的接口语义、异常行为与源码实现解析

JSON for Modern C++:json_pointer::operator== 相等性比较的接口语义、异常行为与源码实现解析

2026-09-07 17:26:01作者:邵娇湘

在 C++ 中操作 JSON 文档时,nlohmann::json_pointer(JSON Pointer,RFC 6901 风格的引用路径)常被用来定位、比较和构造文档中的值。本文围绕 JSON for Modern C++ 的 API 文档 operator== 展开,完整覆盖两个 operator== 重载的签名、参数与返回值语义、异常安全保证和复杂度特性,并结合 json_pointer.hpp 中的实际实现,说明“按引用 token 序列比较”这一底层机制如何决定比较结果,以及字符串比较重载为何会触发 parse_error.107/108 异常、为何在 3.11.2 版本中被标记弃用。读完本文,你可以准确使用该运算符、正确捕获字符串转换过程中的异常,并理解 C++20 三向比较引入前后两套声明形态的差异。

接口声明:两个重载的完整签名

文档给出的声明分为 C++20 之前(自由函数模板)与 C++20 起(成员函数)两种形态:

// until C++20
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
bool operator==(
    const json_pointer<RefStringTypeLhs>& lhs,
    const json_pointer<RefStringTypeRhs>& rhs) noexcept;            // (1)

template<typename RefStringTypeLhs, typename StringType>
bool operator==(
    const json_pointer<RefStringTypeLhs>& lhs,
    const StringType& rhs);                                         // (2)

template<typename RefStringTypeRhs, typename StringType>
bool operator==(
    const StringType& lhs,
    const json_pointer<RefStringTypeRhs>& rhs);                     // (2)

// since C++20
class json_pointer {
    template<typename RefStringTypeRhs>
    bool operator==(
        const json_pointer<RefStringTypeRhs>& rhs) const noexcept;  // (1)

    bool operator==(const string_t& rhs) const;                     // (2)
};

两个重载的语义(文档 “Parameters/Return value” 部分):

  • 重载 (1):比较两个 JSON Pointer 是否相等,比较依据是它们的引用 token(reference token)序列
  • 重载 (2):将一个 JSON Pointer 与字符串(或字符串与 JSON Pointer)比较,先把字符串转换成 JSON Pointer,再按重载 (1) 的规则比较。

模板参数说明:RefStringTypeLhsRefStringTypeRhs 分别是左/右操作数 JSON Pointer 的字符串类型;StringType 是由 json_pointer 操作数推导出的字符串类型,即 json_pointer::string_t。参数 lhsrhs 分别为要比较的第一个和第二个值,返回值即二者是否相等。

在源码层面,json_pointer.hpp 用宏 JSON_HAS_THREE_WAY_COMPARISON 切换了两套声明:C++20 下是类内成员函数(json_pointer.hpp#L976-L998),非 C++20 下是类内声明、类外定义的自由函数(json_pointer.hpp#L1002-L1019,定义位于 json_pointer.hpp#L1054-L1079)。文档中注明 C++20 成员函数自 3.11.2 版本加入,与源码中的版本注释一致。

比较的核心语义:按引用 token 序列逐一对比

operator== 的相等性不取决于原始字符串是否逐字符相同,而取决于解析后的引用 token 序列是否一致。从源码结构看,这一点非常直接:

// include/nlohmann/detail/json_pointer.hpp
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
inline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
                       const json_pointer<RefStringTypeRhs>& rhs) noexcept
{
    return lhs.reference_tokens == rhs.reference_tokens;
}

reference_tokensjson_pointer 的私有成员 std::vector<string_t>json_pointer.hpp#L1049-L1052)。构造函数把字符串交给 split() 分解为 token 序列(json_pointer.hpp#L60-L64),因此 """/foo""/foo/0" 这类指针内部保存的都是已还原转义的 token 列表。比较时等价于对两个 std::vector<string_t> 做逐元素相等判断:

  • 两侧 token 数量不同,比较在长度判断处即结束(常量时间);
  • 数量相同时逐个 token 比较(线性时间)。

这正是文档 “Complexity” 一节所述:“若 lhsrhs 的引用 token 数量不同则为常量复杂度,否则与引用 token 数量成线性复杂度。”

字符串比较重载:先转换再比较,并可能抛异常

重载 (2) 的实现同样简短——先把字符串隐式构造为同类型的 json_pointer,再走重载 (1):

template<typename RefStringTypeLhs,
         typename StringType = typename json_pointer<RefStringTypeLhs>::string_t>
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))
inline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
                       const StringType& rhs)
{
    return lhs == json_pointer<RefStringTypeLhs>(rhs);
}

关键在于字符串到 json_pointer 的转换发生在 split() 中,它会对非法指针字符串抛出异常(json_pointer.hpp#L789-L840),因此重载 (1) 是无异常保证的,而重载 (2) 可能抛出 parse_error

异常 触发条件 源码位置
parse_error.107 给定的 JSON Pointer 字符串非空且未以 / 开头 json_pointer.hpp#L805
parse_error.108 字符串中的 ~ 后面不是 0(代表 ~)或 1(代表 / json_pointer.hpp#L840

这解释了文档 “Exception safety” 一节的两条差异:重载 (1) 是 No-throw guarantee(永不抛异常);重载 (2) 是 strong exception safety——若发生异常,原值保持完好(因为比较不修改任何操作数)。

可运行示例一:比较两个 JSON Pointer(重载 1)

以下示例来自 json_pointer__operator__equal.cpp,可直接编译运行(头文件模式包含 single_include/nlohmann/json.hpp 即可):

#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main()
{
    // different JSON pointers
    json::json_pointer ptr0;      // 空指针,指向整个文档
    json::json_pointer ptr1("");  // 显式传入空串,与 ptr0 等价
    json::json_pointer ptr2("/foo");

    // compare JSON pointers
    std::cout << std::boolalpha
              << "\"" << ptr0 << "\" == \"" << ptr0 << "\": " << (ptr0 == ptr0) << '\n'
              << "\"" << ptr0 << "\" == \"" << ptr1 << "\": " << (ptr0 == ptr1) << '\n'
              << "\"" << ptr1 << "\" == \"" << ptr2 << "\": " << (ptr1 == ptr2) << '\n'
              << "\"" << ptr2 << "\" == \"" << ptr2 << "\": " << (ptr2 == ptr2) << std::endl;
}

输出(见 json_pointer__operator__equal.output):

"" == "": true
"" == "": true
"" == "/foo": false
"/foo" == "/foo": true

注意 ptr0ptr1 的来源不同(默认构造 vs. 空字符串构造),但二者 token 序列都为空,因此比较结果为 true——这印证了“比较的是 token 序列而非构造方式”的语义。

可运行示例二:比较 JSON Pointer 与字符串(重载 2,注意异常)

第二个示例来自 json_pointer__operator__equal_stringtype.cpp,展示了字符串比较以及非法字符串触发异常的情形:

#include <exception>
#include <iostream>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

int main()
{
    // different JSON pointers
    json::json_pointer ptr0;
    json::json_pointer ptr1("");
    json::json_pointer ptr2("/foo");

    // different strings
    std::string str0("");
    std::string str1("/foo");
    std::string str2("bar");

    // compare JSON pointers and strings
    std::cout << std::boolalpha
              << "\"" << ptr0 << "\" == \"" << str0 << "\": " << (ptr0 == str0) << '\n'
              << "\"" << str0 << "\" == \"" << ptr1 << "\": " << (str0 == ptr1) << '\n'
              << "\"" << ptr2 << "\" == \"" << str1 << "\": " << (ptr2 == str1) << std::endl;

    try
    {
        std::cout << "\"" << str2 << "\" == \"" << ptr2 << "\": " << (str2 == ptr2) << std::endl;
    }
    catch (const json::parse_error& ex)
    {
        std::cout << ex.what() << std::endl;
    }
}

输出(见 json_pointer__operator__equal_stringtype.output):

"" == "": true
"" == "": true
"/foo" == "/foo": true
"bar" == "/foo": [json.exception.parse_error.107] parse error at byte 1: JSON pointer must be empty or begin with '/' - was: 'bar'

最后一行正是 parse_error.107:字符串 "bar" 非空且不以 / 开头,split() 在转换阶段即抛出异常,比较从未发生。异常信息中还带出了出错字节位置(byte 1),与库的 异常诊断 机制一致。

弃用说明与 C++20 演进

文档在 “Notes” 中给出明确警告:重载 (2)(字符串比较)已被弃用,并将在未来的大版本中移除。源码中对应变了双重体现:

  1. 所有字符串比较重载都标注了 JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))json_pointer.hpp#L987-L991json_pointer.hpp#L1063-L1079),编译时若启用弃用警告会提示迁移到“先显式构造 json_pointer 再比较两个指针”的写法;
  2. C++20 下(JSON_HAS_THREE_WAY_COMPARISON 成立),库额外提供了成员形式 operator== 以及成员 operator<=>json_pointer.hpp#L993-L998),!= 等比较可由三向比较推导,与文档 “Version history” 中“C++20 成员函数于 3.11.2 加入”的记录对应。

迁移建议(由弃用注解可推断):将 ptr == "/foo" 改写为 ptr == json::json_pointer("/foo"),既消除弃用警告,又让转换时机与异常行为显式化。

版本历史与相关接口

文档 “Version history” 一节:

  1. 重载 (1):2.1.0 版本加入;C++20 成员函数于 3.11.2 加入;
  2. 重载 (2):为向后兼容而加入,3.11.2 版本起弃用。

围绕 JSON Pointer 的其余比较与操纵接口,可参考同一 API 目录下的文档:operator!=string_tjson_pointer 类总览,实现均集中在 json_pointer.hpp 中。本文基于当前仓库(版本 3.12.0,见 json_pointer.hpp 文件头注释)核实,所有行号与宏名均可在源码中直接定位验证。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.74 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.81 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
595
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
920
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.63 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
518
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
389