JsonCPP源码分析——Value

1、Json::Value类中有一个自定义的字符串结构体,这个结构体的概念是非常重要的,设计的非常巧妙,首先介绍下这个结构体,个人理解以注释的方式添加到下面的代码中:

 class CZString {
  public:
    // 枚举,标识字符串类型的key,有没有payload,也可以理解为,字符串的首地址cstr_后面的空间是不是自己所拥有,
    // 因为有些移动语义会共用其它CZString对象的空间
    enum DuplicationPolicy { noDuplication = 0, duplicate, duplicateOnCopy };
    //构造,感觉国内很少用char const*,更多的用 const char*,可能写库的大佬们认为char const*,一眼就可以看出是常量指针吧 
    CZString(ArrayIndex index);
    CZString(char const* str, unsigned length, DuplicationPolicy allocate); 
    // 拷贝构造
    CZString(CZString const& other);
    CZString(CZString&& other) noexcept; // 不抛异常
    // 析构
    ~CZString();
    // 赋值
    CZString& operator=(const CZString& other);
    CZString& operator=(CZString&& other) noexcept;
    // 自定义比较规则,因为底层用map实现,map的key要求重载<
    bool operator<(CZString const& other) const;
    bool operator==(CZString const& other) const;
    // 返回数组index
    ArrayIndex index() const;
    // const char* c_str() const; ///< \deprecated
    // 返回cstr_
    char const* data() const;
    // key是string时,返回字符串长度
    unsigned length() const;
    bool isStaticString() const;

  private:
    void swap(CZString& other);
    // policy_是枚举值,标明,cstr_指向空间的属性
    // length_ 字符串的长度
    // 这种C语言定义结构体的方式值得学习,可以节省空间
    struct StringStorage {
      unsigned policy_ : 2;
      unsigned length_ : 30; // 1GB max
    };
    // 数组cstr_是nullptr, object时是非空
    char const* cstr_; // actually, a prefixed string, unless policy is noDup
    // 联合体,程序员自己解释
    union {
      ArrayIndex index_;
      StringStorage storage_;
    };
  };

2、Json::Value类是Jsoncpp中一个至关重要的类,该类定义了数据的存储方式,可以理解为读写都是对该类进行的。在下面的代码中记录了一些个人认为值得学习的地方(只针对博主的知识储备而言,可能大佬觉着比较简单)。

// 支持的数据类型
enum ValueType {
  nullValue = 0, ///< 'null' value
  intValue,      ///< signed integer value
  uintValue,     ///< unsigned integer value
  realValue,     ///< double value
  stringValue,   ///< UTF-8 string value
  booleanValue,  ///< bool value
  arrayValue,    ///< array value (ordered list)
  objectValue    ///< object value (collection of name/value pairs).
};
// 利用type构造函数时,当type是字符串时,先将空字符串指针赋值给string_
// 这里逻辑上可能实现了闭环,如果自己使用的话,不要将const char* 指针赋值给char*,如果程序员用返回的指针修改空间的值
// 将会使const丧失作用,会造成未定义的行为,这种bug是最难查的。
Value::Value(ValueType type) {
  static char const emptyString[] = ""; // 静态,提高效率
  switch (type) {
  ...
  case stringValue:
    // allocated_ == false, so this is safe.
    value_.string_ = const_cast<char*>(static_cast<char const*>(emptyString));
  ...
}
// 字符串的开始是字符串的长度,这种做法是值得学习的
// 下次再看到这,可以顺便复习下static_cast 和 reinterpret_cast
static inline char* duplicateAndPrefixStringValue(const char* value,
                                                  unsigned int length) {
  // Avoid an integer overflow in the call to malloc below by limiting length
  // to a sane value.
  JSON_ASSERT_MESSAGE(length <= static_cast<unsigned>(Value::maxInt) -
                                    sizeof(unsigned) - 1U,
                      "in Json::Value::duplicateAndPrefixStringValue(): "
                      "length too big for prefixing");
  size_t actualLength = sizeof(length) + length + 1;
  auto newString = static_cast<char*>(malloc(actualLength));
  if (newString == nullptr) {
    throwRuntimeError("in Json::Value::duplicateAndPrefixStringValue(): "
                      "Failed to allocate string value buffer");
  }
  *reinterpret_cast<unsigned*>(newString) = length;
  memcpy(newString + sizeof(unsigned), value, length);
  newString[actualLength - 1U] =
      0; // to avoid buffer over-run accidents by users later
  return newString;
}
// 读取数据
 String asString() const; 
 Int asInt() const;
 UInt asUInt() const;
#if defined(JSON_HAS_INT64)
 Int64 asInt64() const;
 UInt64 asUInt64() const;
#endif // if defined(JSON_HAS_INT64)
 LargestInt asLargestInt() const;
 LargestUInt asLargestUInt() const;
 float asFloat() const;
 double asDouble() const;
 bool asBool() const;
// 这个的作用将在下一篇博客中进行介绍
explicit operator bool() const;
// 针对数组的接口函数
Value& append(const Value& value);
Value& append(Value&& value);
bool insert(ArrayIndex index, const Value& newValue);
bool insert(ArrayIndex index, Value&& newValue);
//真正存放数据的地方
private:
union ValueHolder {
  LargestInt int_;
  LargestUInt uint_;
  double real_;
  bool bool_;
  char* string_; // if allocated_, ptr to { unsigned, char[] }.
  ObjectValues* map_;
} value_;
// value_type_是数据类型,allocated_表示是否为 string_分配内存
struct {
  // Really a ValueType, but types should agree for bitfield packing.
  unsigned int value_type_ : 8;
  // Unless allocated_, string_ must be null-terminated.
  unsigned int allocated_ : 1;
} bits_;
// 注释类,注释可以在value的上一行,行内,下一行,有些中间件{ // 是不合法的,jsoncpp还没试,感觉也不合法
class Comments {
public:
  Comments() = default;
  Comments(const Comments& that);
  Comments(Comments&& that) noexcept;
  Comments& operator=(const Comments& that);
  Comments& operator=(Comments&& that) noexcept;
  bool has(CommentPlacement slot) const;
  String get(CommentPlacement slot) const;
  void set(CommentPlacement slot, String comment);

private:
  using Array = std::array<String, numberOfCommentPlacement>;
  std::unique_ptr<Array> ptr_;
};
  Comments comments_;

// 当前Value在整体的Json中的开始偏移,是左闭右开的
ptrdiff_t start_;
ptrdiff_t limit_;

3、Path和PathArgument,个人觉得设计的非常好,可以理解为通过模版的方式动态的获取json中的值。先举个例子,代码如下:

// 现有json如下,要查找Alice的第一个或者第二个朋友
// {
//   "user": {
//    "profile": {
//      "name": "Alice",
//      "age": 30,
//      "friends": [
//        "Bob",
//        "Carol"
//      ]
//    }
//  }
// }
#include <iostream>
#include "value.h" // 假设你已经有 jsoncpp中path模块

int main() {
    Value root = ...; // 你的 JSON 数据
    int index = 1; // 用户指定的索引
    PathArgument indexArg(index);
    Path path(".user.profile.friends[%]", indexArg);

    // 获取指定索引的朋友名字
    const Value& friendName = path.resolve(root);
    std::cout << "Friend: " << friendName.asString() << std::endl;
    return 0;
}

Jsoncpp中实现了这种路径的方式来获取或者指定json中的值,JsonCpp中Path和PathArgument的定义如下:

class JSON_API PathArgument {
public:
  friend class Path;

  PathArgument();
  PathArgument(ArrayIndex index);
  PathArgument(const char* key);
  PathArgument(String key);

private:
  // 表示占位符是什么类型的
  enum Kind { kindNone = 0, kindIndex, kindKey };
  String key_;
  ArrayIndex index_{};
  Kind kind_{kindNone};
};

/** \brief Experimental and untested: represents a "path" to access a node.
 *
 * Syntax:
 * - "." => root node
 * - ".[n]" => elements at index 'n' of root node (an array value)
 * - ".name" => member named 'name' of root node (an object value)
 * - ".name1.name2.name3"
 * - ".[0][1][2].name1[3]"
 * - ".%" => member name is provided as parameter
 * - ".[%]" => index is provided as parameter
 */
class JSON_API Path {
public:
  Path(const String& path, const PathArgument& a1 = PathArgument(),
       const PathArgument& a2 = PathArgument(),
       const PathArgument& a3 = PathArgument(),
       const PathArgument& a4 = PathArgument(),
       const PathArgument& a5 = PathArgument());

  const Value& resolve(const Value& root) const;
  Value resolve(const Value& root, const Value& defaultValue) const;
  /// Creates the "path" to access the specified node and returns a reference on
  /// the node.
  Value& make(Value& root) const;

private:
  using InArgs = std::vector<const PathArgument*>;
  using Args = std::vector<PathAArgument>;
  // 这个函数是实现该算法的关键,其实就是比较巧妙的字符串处理
  void makePath(const String& path, const InArgs& in);
  void addPathInArg(const String& path, const InArgs& in,
                    InArgs::const_iterator& itInArg, PathArgument::Kind kind);
  static void invalidPath(const String& path, int location);

  Args args_;
};

4、value.h中还针对当前的数据结构设计了迭代器(说到迭代器,就要想到必备的几大要素,类型,指针,引用,距离等),异常处理等,下面是异常处理的代码:

class JSON_API Exception : public std::exception {
public:
  Exception(String msg);
  ~Exception() noexcept override;
  char const* what() const noexcept override;

protected:
  String msg_;
};
class JSON_API RuntimeError : public Exception {
public:
  RuntimeError(String const& msg);
};

5、这个模块中还涉及到一些数值计算(补码反码)、跨平台编程(宏)、定义不返回的函数[[noreturn]]、告诉编译器直接计算当表达式的值(constexpr)、不抛出异常(noexcept),只能通过显示转换(explicit)等技巧,这些在C++专栏中有介绍。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值