Bootstrap

nlohmann json C++ 解析

学习材料:nlohmann json

json官方

源码解析

源码

要学习并理解这份代码,可以按照以下步骤进行,逐步梳理代码的逻辑:

基本步骤:

  1. 配置宏:

    • 理解用于配置的宏定义,这些宏控制库的不同特性和行为。
    • 例如,#if JSON_DIAGNOSTICS 用于控制诊断特性的启用。
  2. 核心类 basic_json:

    • 分析 basic_json 类,它是整个库的核心,表示一个 JSON 值。
    • 理解类中的成员变量(如 value_t 枚举和 json_value 联合体)和它们的作用。
    • 阅读并理解类的构造函数、析构函数、拷贝和移动构造函数及赋值运算符。
    • 学习类中的成员函数,例如 operator[]getsetis 等。
  3. 序列化和反序列化:

    • 理解用于将 JSON 值转换为字符串和从字符串转换回 JSON 值的序列化和反序列化逻辑。
    • 例如,json_serializer 类中的 serializedeserialize 方法。
  4. 异常处理:

    • 了解用于处理错误的自定义异常类,例如 json_exception
    • 理解这些异常类如何与标准的异常处理机制集成,并提供有用的错误信息。

basic_json

The basic_json class is the core of the library, encapsulating all JSON types. Its design includes:

Type Management: An internal enum to manage the type of the JSON value.
Storage: A union to store different types efficiently.
Constructors and Destructors: To handle different JSON types.
Operators: Overloaded operators for ease of use.

// basic_json 类用于表示一个 JSON 值
template<typename BasicJsonType>
class basic_json
{
private:
    json_value m_value; // 存储 JSON 值的联合体
    value_t m_type = value_t::null; // 当前 JSON 值的类型

public:
    // 构造函数
    basic_json() noexcept;
    basic_json(value_t t) noexcept;

    // 析构函数
    ~basic_json() noexcept;

    // 拷贝和移动构造函数及赋值运算符
    basic_json(const basic_json& other);
    basic_json(basic_json&& other) noexcept;

    basic_json& operator=(const basic_json& other);
    basic_json& operator=(basic_json&& other) noexcept;

    // 成员函数
    // 示例:重载运算符 []
    reference operator[](size_type idx)
    {
        // 检查类型并返回数组元素
        if (JSON_UNLIKELY(!is_array()))
        {
            throw type_error::create(305, "cannot use operator[] with " + std::string(type_name()));
        }

        return m_value.array->operator[](idx);
    }
};

类型管理

JSON typevalue_t typeused type
objectobjectpointer to @ref object_t
arrayarraypointer to @ref array_t
stringstringpointer to @ref string_t
booleanboolean@ref boolean_t
numbernumber_integer@ref number_integer_t
numbernumber_unsigned@ref number_unsigned_t
numbernumber_float@ref number_float_t
binarybinarypointer to @ref binary_t
nullnullno value is stored

定义一个枚举来表示 JSON 值的类型

enum class value_t : std::uint8_t
{
    null,             ///< null value
    object,           ///< object (unordered set of name/value pairs)
    array,            ///< array (ordered collection of values)
    string,           ///< string value
    boolean,          ///< boolean value
    number_integer,   ///< number value (signed integer)
    number_unsigned,  ///< number value (unsigned integer)
    number_float,     ///< number value (floating-point)
    binary,           ///< binary array (ordered collection of bytes)
    discarded         ///< discarded by the parser callback function
};

C++11枚举类——enum class

联合体用于高效存储不同类型的 JSON 值

    using string_t = StringType;
    using boolean_t = BooleanType;
    using number_integer_t = NumberIntegerType;
    using number_unsigned_t = NumberUnsignedType;
    using number_float_t = NumberFloatType;
    using binary_t = nlohmann::byte_container_with_subtype<BinaryType>;
    using object_comparator_t = detail::actual_object_comparator_t<basic_json>;
    
union json_value
{
    std::nullptr_t null;
    object_t* object;
    array_t* array;
    string_t* string;
    boolean_t boolean;
    number_integer_t number_integer;
    number_unsigned_t number_unsigned;
    number_float_t number_float;
    binary_t* binary;
    //构造函数
	json_value() = default;
     json_value(boolean_t v) noexcept : boolean(v) {}
     json_value(number_integer_t v) noexcept : number_integer(v) {}
     json_value(number_unsigned_t v) noexcept : number_unsigned(v) {}
     json_value(number_float_t v) noexcept : number_float(v) {}
      json_value(value_t t)
      {
          switch (t)
          {
              case value_t::object:
              {
                  object = create<object_t>();
                  break;
              }

              case value_t::array:
              {
                  array = create<array_t>();
                  break;
              }

              case value_t::string:
              {
                  string = create<string_t>("");
                  break;
              }
.......
      }
    // 联合体的构造函数
    json_value() noexcept : null(nullptr) {}
};

Explanation of the Code

The code snippet you provided is a part of the nlohmann/json library’s template declarations and type definitions. Let’s break it down step-by-step.

1. number_integer_t
using number_integer_t = NumberIntegerType;

This line is a type alias declaration in C++. It creates an alias number_integer_t for NumberIntegerType. Essentially, wherever number_integer_t is used, it refers to NumberIntegerType.

2. Template Declaration Macro
#define NLOHMANN_BASIC_JSON_TPL_DECLARATION                                \
    template<template<typename, typename, typename...> class ObjectType,   \
             template<typename, typename...> class ArrayType,              \
             class StringType, class BooleanType, class NumberIntegerType, \
             class NumberUnsignedType, class NumberFloatType,              \
             template<typename> class AllocatorType,                       \
             template<typename, typename = void> class JSONSerializer,     \
             class BinaryType,                                             \
             class CustomBaseClass>

This macro defines a template declaration for the basic_json class template. It simplifies the repeated use of this long template parameter list by allowing the declaration to be inserted with a macro.

3. NumberIntegerType Definition
class NumberIntegerType = std::int64_t;

This line defines NumberIntegerType as a class that is actually an alias for std::int64_t. This indicates that the default type for representing integer numbers in JSON is std::int64_t, a 64-bit signed integer.

Reasons for This Design
a. Template Flexibility

The use of template parameters for various types (like NumberIntegerType) allows the basic_json class to be highly flexible. Users of the library can customize the JSON value types according to their needs. For instance, they can choose different types for integers, floats, strings, etc., based on specific requirements like memory constraints or performance needs.

b. Type Aliases for Readability

The using alias (using number_integer_t = NumberIntegerType) improves code readability and maintainability. It allows the library to use meaningful names throughout the implementation, making it clear what type each alias represents.

c. Macros for Simplification

The macro NLOHMANN_BASIC_JSON_TPL_DECLARATION is used to simplify the repeated declaration of the template parameter list. This avoids redundancy and potential errors from copying the long template parameter list multiple times in the code.

Example Usage

Here’s a simplified example of how these templates and type aliases might be used in the basic_json class:

NLOHMANN_BASIC_JSON_TPL_DECLARATION
class basic_json
{
public:
    using integer_t = NumberIntegerType; // Using the template parameter type
    using unsigned_t = NumberUnsignedType;
    using float_t = NumberFloatType;

    // Example function using the type aliases
    void set_number(integer_t value)
    {
        m_value.number_integer = value;
        m_type = value_t::number_integer;
    }

private:
    union json_value
    {
        integer_t number_integer;
        unsigned_t number_unsigned;
        float_t number_float;
        // Other types...
    } m_value;

    value_t m_type = value_t::null;
};

In this example:

  • The basic_json class template is declared using the macro NLOHMANN_BASIC_JSON_TPL_DECLARATION.
  • The type aliases integer_t, unsigned_t, and float_t are used within the class to refer to the respective types specified by the template parameters.
  • A member function set_number demonstrates how the type alias integer_t is used to define function parameters and manipulate the union member number_integer.
Summary

This design approach provides flexibility, readability, and maintainability. It allows the basic_json class to be easily customized for different types while keeping the code clean and understandable. The use of type aliases and macros ensures that the code remains concise and reduces the risk of errors from repetitive code.

成员变量

	struct data
    {
        /// the type of the current element
        value_t m_type = value_t::null;

        /// the value of the current element
        json_value m_value = {};

        data(const value_t v)
            : m_type(v), m_value(v)
        {
        }

        data(size_type cnt, const basic_json& val)
            : m_type(value_t::array)
        {
            m_value.array = create<array_t>(cnt, val);
        }

        data() noexcept = default;
        data(data&&) noexcept = default;
        data(const data&) noexcept = delete;
        data& operator=(data&&) noexcept = delete;
        data& operator=(const data&) noexcept = delete;

        ~data() noexcept
        {
            m_value.destroy(m_type);
        }
    };

解释:

data(const value_t v)
   : m_type(v), m_value(v)
 {
 }

输入是 json类型,m_type好理解。看m_value
看json_value的构造函数:

json_value(value_t t)
{
    switch (t)
    {
        case value_t::object:
        {
            object = create<object_t>();
            break;
        }

create是个模板函数

    /// helper for exception-safe object creation
    template<typename T, typename... Args>
    JSON_HEDLEY_RETURNS_NON_NULL
    static T* create(Args&& ... args)
    {
        AllocatorType<T> alloc;
        using AllocatorTraits = std::allocator_traits<AllocatorType<T>>;

        auto deleter = [&](T * obj)
        {
            AllocatorTraits::deallocate(alloc, obj, 1);
        };
        std::unique_ptr<T, decltype(deleter)> obj(AllocatorTraits::allocate(alloc, 1), deleter);
        AllocatorTraits::construct(alloc, obj.get(), std::forward<Args>(args)...);
        JSON_ASSERT(obj != nullptr);
        return obj.release();
    }

This code is a C++ template function that helps create objects in an exception-safe manner. Let’s break down the key parts of the code step by step:

  1. Template Definition
template<typename T, typename... Args>
  • T: The type of object to be created.
  • Args...: A variadic template parameter pack that allows passing multiple arguments to the constructor of T.
  1. Function Signature
JSON_HEDLEY_RETURNS_NON_NULL
static T* create(Args&& ... args)
  • JSON_HEDLEY_RETURNS_NON_NULL: This is likely a macro that indicates the function returns a non-null pointer.
  • static T* create(Args&& ... args): The function is static and returns a pointer to T.
  1. Allocator Definition
AllocatorType<T> alloc;
using AllocatorTraits = std::allocator_traits<AllocatorType<T>>;
  • AllocatorType<T>: Defines an allocator type for T. It could be a custom allocator or std::allocator<T>.
  • AllocatorTraits: Defines the allocator traits for AllocatorType<T>. std::allocator_traits provides a uniform interface to allocator types.
  1. Deleter Lambda
auto deleter = [&](T * obj)
{
    AllocatorTraits::deallocate(alloc, obj, 1);
};
  • deleter: A lambda function that deallocates memory using the allocator traits.
  1. Unique Pointer with Custom Deleter
std::unique_ptr<T, decltype(deleter)> obj(AllocatorTraits::allocate(alloc, 1), deleter);
  • A std::unique_ptr is created with a custom deleter to manage the allocated memory. The memory is allocated using AllocatorTraits::allocate.
  1. Object Construction
AllocatorTraits::construct(alloc, obj.get(), std::forward<Args>(args)...);
  • Constructs the object in the allocated memory using AllocatorTraits::construct and perfect forwarding the arguments.
  1. Assertion and Return
JSON_ASSERT(obj != nullptr);
return obj.release();
  • JSON_ASSERT(obj != nullptr): Ensures that the object is not null.
  • obj.release(): Releases the ownership of the pointer from the std::unique_ptr, so it won’t be automatically deleted.

The function create is designed to safely allocate and construct an object of type T using a custom or standard allocator. It ensures that if any exceptions are thrown during the construction of the object, the allocated memory will be properly deallocated, preventing memory leaks.

Here’s the function again for reference:

/// helper for exception-safe object creation
template<typename T, typename... Args>
JSON_HEDLEY_RETURNS_NON_NULL
static T* create(Args&& ... args)
{
    AllocatorType<T> alloc;
    using AllocatorTraits = std::allocator_traits<AllocatorType<T>>;

    auto deleter = [&](T * obj)
    {
        AllocatorTraits::deallocate(alloc, obj, 1);
    };
    std::unique_ptr<T, decltype(deleter)> obj(AllocatorTraits::allocate(alloc, 1), deleter);
    AllocatorTraits::construct(alloc, obj.get(), std::forward<Args>(args)...);
    JSON_ASSERT(obj != nullptr);
    return obj.release();
}

Summary

  • Allocator Handling: Uses an allocator to manage memory for the object.
  • Exception Safety: Ensures that memory is deallocated if an exception occurs during construction.
  • Unique Pointer: Utilizes a std::unique_ptr with a custom deleter to manage the allocated memory safely.
  • Assertion: Checks that the object is successfully created before returning it.

如果我们直接把 create 函数模板中的 T 替换为 int,那么这个模板实例化后的代码如下:

模板实例化后的代码

#include <iostream>
#include <memory>
#include <cassert>
#include <utility>

// 假设 JSON_HEDLEY_RETURNS_NON_NULL 和 JSON_ASSERT 宏定义如下
#define JSON_HEDLEY_RETURNS_NON_NULL
#define JSON_ASSERT(x) assert(x)

// 定义 create 函数
JSON_HEDLEY_RETURNS_NON_NULL
static int* create(int&& arg)
{
    using AllocatorType = std::allocator<int>;
    AllocatorType alloc;
    using AllocatorTraits = std::allocator_traits<AllocatorType>;

    auto deleter = [&](int* obj)
    {
        AllocatorTraits::deallocate(alloc, obj, 1);
    };
    std::unique_ptr<int, decltype(deleter)> obj(AllocatorTraits::allocate(alloc, 1), deleter);
    AllocatorTraits::construct(alloc, obj.get(), std::forward<int>(arg));
    JSON_ASSERT(obj != nullptr);
    return obj.release();
}

int main()
{
    // 使用 create 函数创建一个 int 型对象
    int* myInt = create(42);

    // 打印结果
    std::cout << "Created int: " << *myInt << std::endl;

    // 释放内存
    delete myInt;

    return 0;
}

为什么C++20中要移除std::alloctor的construct与destory
提供了一种更安全的方式来管理内存分配和释放。其核心思想是利用智能指针和自定义删除器,确保在对象创建过程中如果发生异常,分配的内存能够被正确释放。

异常安全: 在 AllocatorTraits::construct 过程中,如果发生异常,std::unique_ptr 的自定义删除器会自动调用 AllocatorTraits::deallocate 来释放内存,避免内存泄漏。
自动管理内存: 智能指针(如 std::unique_ptr)自动管理其所拥有的内存,减少手动释放内存的可能错误。

32|容器里的内存管理:分配器

构造函数

https://json.nlohmann.me/api/basic_json/basic_json/
一共有9个构造函数

  1. ` basic_json(const value_t v)
    m_data(v)
    {
    assert_invariant();
    }
    `
    m_data(v)在上一节解释过了
  2. ` basic_json(std::nullptr_t = nullptr) noexcept // NOLINT(bugprone-exception-escape)

    basic_json(value_t::null)
    {
    assert_invariant();
    }`

    basic_json(value_t::null)是C++11的委托构造函数

  3. basic_json(CompatibleType && val)
 template < typename CompatibleType,
               typename U = detail::uncvref_t<CompatibleType>,
               detail::enable_if_t <
                   !detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value, int > = 0 >
    basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
                JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
                                           std::forward<CompatibleType>(val))))
    {
        JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val));
        set_parents();
        assert_invariant();
    }

json_serializer

// json_serializer 类用于 JSON 值的序列化和反序列化
template<typename BasicJsonType>
class json_serializer
{
public:
    static void serialize(const BasicJsonType& j, string_t& s)
    {
        // 序列化实现
    }

    static void deserialize(const string_t& s, BasicJsonType& j)
    {
        // 反序列化实现
    }
};

// json_exception 类用于处理 JSON 库中的异常
class json_exception : public std::exception
{
public:
    explicit json_exception(const std::string& msg) : m_msg(msg) {}

    const char* what() const noexcept override
    {
        return m_msg.c_str();
    }

private:
    std::string m_msg;
};

代码

未完待续

;