学习材料:nlohmann json
源码解析
要学习并理解这份代码,可以按照以下步骤进行,逐步梳理代码的逻辑:
基本步骤:
-
配置宏:
- 理解用于配置的宏定义,这些宏控制库的不同特性和行为。
- 例如,
#if JSON_DIAGNOSTICS
用于控制诊断特性的启用。
-
核心类
basic_json
:- 分析
basic_json
类,它是整个库的核心,表示一个 JSON 值。 - 理解类中的成员变量(如
value_t
枚举和json_value
联合体)和它们的作用。 - 阅读并理解类的构造函数、析构函数、拷贝和移动构造函数及赋值运算符。
- 学习类中的成员函数,例如
operator[]
、get
、set
、is
等。
- 分析
-
序列化和反序列化:
- 理解用于将 JSON 值转换为字符串和从字符串转换回 JSON 值的序列化和反序列化逻辑。
- 例如,
json_serializer
类中的serialize
和deserialize
方法。
-
异常处理:
- 了解用于处理错误的自定义异常类,例如
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 type | value_t type | used type |
---|---|---|
object | object | pointer to @ref object_t |
array | array | pointer to @ref array_t |
string | string | pointer to @ref string_t |
boolean | boolean | @ref boolean_t |
number | number_integer | @ref number_integer_t |
number | number_unsigned | @ref number_unsigned_t |
number | number_float | @ref number_float_t |
binary | binary | pointer to @ref binary_t |
null | null | no 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
};
联合体用于高效存储不同类型的 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 macroNLOHMANN_BASIC_JSON_TPL_DECLARATION
. - The type aliases
integer_t
,unsigned_t
, andfloat_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 aliasinteger_t
is used to define function parameters and manipulate the union membernumber_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:
- 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 ofT
.
- 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 toT
.
- Allocator Definition
AllocatorType<T> alloc;
using AllocatorTraits = std::allocator_traits<AllocatorType<T>>;
AllocatorType<T>
: Defines an allocator type forT
. It could be a custom allocator orstd::allocator<T>
.AllocatorTraits
: Defines the allocator traits forAllocatorType<T>
.std::allocator_traits
provides a uniform interface to allocator types.
- Deleter Lambda
auto deleter = [&](T * obj)
{
AllocatorTraits::deallocate(alloc, obj, 1);
};
deleter
: A lambda function that deallocates memory using the allocator traits.
- 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 usingAllocatorTraits::allocate
.
- 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.
- 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 thestd::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)自动管理其所拥有的内存,减少手动释放内存的可能错误。
构造函数
https://json.nlohmann.me/api/basic_json/basic_json/
一共有9个构造函数
-
-
` basic_json(const value_t v)
-
m_data(v)
{
assert_invariant();
}
`
m_data(v)在上一节解释过了
-
m_data(v)
-
-
` 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的委托构造函数
-
- 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;
};
代码
未完待续