RapidJson踩坑记录

RapidJson踩坑记录用于记录 RapidJson 使用中的坑位 持续更新 关于 rapidjson 的详细说明 可以参加参考文档 http rapidjson org zh cn md doc tutorial 8zh cn html CreateString 添加字符串元素现象 include rapidjson document h include rapidjson prettywrite

用于记录RapidJson使用中的坑位,持续更新。关于rapidjson的详细说明,可以参加参考文档:http://rapidjson.org/zh-cn/md_doc_tutorial_8zh-cn.html#CreateString

1、添加字符串元素

现象:

#include "rapidjson/document.h" #include "rapidjson/prettywriter.h" #include "rapidjson/stringbuffer.h" #include 
  
    using namespace std; using namespace rapidjson; int main() { Document doc; doc.SetObject(); Document::AllocatorType &allocator = doc.GetAllocator(); string value1 = "value1"; doc.AddMember("key1", StringRef(value1.c_str(), value1.size()), allocator); string v = doc["key1"].GetString(); cout << v.c_str() << endl; // 输出为value1 value1 = "abc"; v = doc["key1"].GetString(); cout << v.c_str() << endl; // 输出为abc system("pause"); return 0; } 
  

输出结果证明,上述操作为字符串浅拷贝。所以如果有下面这样的代码,输出将是不确定的值。

void addMem(Document& doc) { string value = ""; doc.AddMember("key", StringRef(value.c_str(), value.size()), doc.GetAllocator()); } int main() { Document doc; doc.SetObject(); addMem(doc); string v = doc["key"].GetString(); cout << v.c_str() << endl; //此处由于局部变量被释放,将输出乱码 system("pause"); return 0; }

原因:

分析原因,首先看两点:

1、StringRef对于字符串的操作

template 
  
    inline GenericStringRef 
   
     StringRef(const CharType* str, size_t length) { return GenericStringRef 
    
      (str, SizeType(length)); } 
     
    
  

这个源码中对于StringRef函数的定义,返回一个GenericStringRef对象。从对应的构造方法中可以看出,其只是暴力的把地址的值浅拷贝到GenericStringRef内维护的char* 变量s中,长度拷贝到length中:

GenericStringRef(const CharType* str, SizeType len) : s(RAPIDJSON_LIKELY(str) ? str : emptyString), length(len) { RAPIDJSON_ASSERT(str != 0 || len == 0u); }

 2、AddMember的实现

在看AddMember是怎么实现的。

GenericValue& AddMember(StringRefType name, StringRefType value, Allocator& allocator) { GenericValue v(value); return AddMember(name, v, allocator); }

首先,通过StringRef返回的StringRefType类型,构造一个GenericValue。

explicit GenericValue(StringRefType s) RAPIDJSON_NOEXCEPT : data_() { SetStringRaw(s); }

然后,构造时,调用了SetStringRaw方法。

RAPIDJSON_FORCEINLINE const Ch* SetStringPointer(const Ch* str) { return RAPIDJSON_SETPOINTER(Ch, data_.s.str, str); }

经过一系列调用,最终调用到这里。查看RAPIDJSON_SETPOINTER宏,可以看到,依然是暴力的浅拷贝。

#define RAPIDJSON_SETPOINTER(type, p, x) (p = (x))

 有了一个浅拷贝的GenericValue,最后看AddMember。同样的,经过一系列调用,最后的RawAssign函数如下:

void RawAssign(GenericValue& rhs) RAPIDJSON_NOEXCEPT { data_ = rhs.data_; // data_.f.flags = rhs.data_.f.flags; rhs.data_.f.flags = kNullFlag; }

 于是我们看到的是,从始至终,一直是浅拷贝的AddMember。所以当给doc添加字符串value时,不要添加局部变量作为入参。同理,key也不要。

正确写法:

1、doc.AddMember("key", "value", doc.GetAllocator()); //直接添加字符串常量,常量区不会释放

2、构造一个Value,添加到doc中,代码如下:

void addMem(Document& doc) { string value = ""; Value v(kStringType); v.SetString(value.c_str(), value.size(), doc.GetAllocator()); //这里很重要,必须要传递allocator作为参数,否则依然为浅拷贝。 doc.AddMember("key", v, doc.GetAllocator()); }

注意allocator的传递。

2、Document Value的拷贝

先看如下代码:

int main() { Document doc; doc.SetObject(); doc.AddMember("key1", "value1", doc.GetAllocator()); doc.AddMember("key2", "value2", doc.GetAllocator()); cout << "key1:" << (doc["key1"].IsNull() ? "NULL" : doc["key1"].GetString()) << endl; cout << "key2:" << (doc["key2"].IsNull() ? "NULL" : doc["key2"].GetString()) << endl; system("pause"); return 0; }

输入出结果如所期,"value1","value2"。一切看上去都很和谐。然后加上一个key3。代码如下

int main() { Document doc; doc.SetObject(); doc.AddMember("key1", "value1", doc.GetAllocator()); doc.AddMember("key2", "value2", doc.GetAllocator()); doc.AddMember("key3", doc["key1"], doc.GetAllocator()); cout << "key1:" << (doc["key1"].IsNull() ? "NULL" : doc["key1"].GetString()) << endl; cout << "key2:" << (doc["key2"].IsNull() ? "NULL" : doc["key2"].GetString()) << endl; cout << "key3:" << (doc["key3"].IsNull() ? "NULL" : doc["key3"].GetString()) << endl; system("pause"); return 0; }

想做的事也很简单,添加一个key3的key,value设置成与key1相同。于是很开心的看是编译运行,然而得到的结果却兵不正确。

RapidJson踩坑记录

key1变成了null,瓦斯则法克。看源码。

其实源码在上个问题中已经贴上了。RapidJson重载了6个AddMember函数,6个函数经过一系列的封装,最终调用的都是如下函数原型:

GenericValue& AddMember(GenericValue& name, GenericValue& value, Allocator& allocator) { RAPIDJSON_ASSERT(IsObject()); RAPIDJSON_ASSERT(name.IsString()); ObjectData& o = data_.o; if (o.size >= o.capacity) MemberReserve(o.capacity == 0 ? kDefaultObjectCapacity : (o.capacity + (o.capacity + 1) / 2), allocator); Member* members = GetMembersPointer(); members[o.size].name.RawAssign(name); members[o.size].value.RawAssign(value); o.size++; return *this; }

而RawAssign的实现,上面已经贴过了。在上个问题中,我们关注的是浅拷贝问题,而在这里我们重点关注这一句话:

rhs.data_.f.flags = kNullFlag;

原值的flag被置为kNullFlag。这个值厉害了,判断doc是否为空就靠它。

bool IsNull() const { return data_.f.flags == kNullFlag; } 

试图去取一个null,会抛出异常,所以做Value直接Add的时候要格外当心。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/229079.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月16日 下午5:31
下一篇 2026年3月16日 下午5:31


相关推荐

  • 什么是Volatile关键字?

    什么是Volatile关键字?一、Java的内存模型(JMM)在仔细讲解Java的volatile关键字之前有必要先了解一下【Java的内存模型】Java的内存模型简称JMM(JavaMemoryModel),是Java虚拟机所定义的一种抽象规范用来屏蔽【不同硬件】和【操作系统】的【内存访问差异】。让Java程序在各种平台下都能达到一致的内存访问效果。…

    2022年7月27日
    8
  • 计算机组成原理核心知识点总结&面试笔试要点[通俗易懂]

    作为一名计算机专业的学生,计算机组成原理、计算机网络、操作系统这三门课程可以说是专业核心基础课,是至关重要的,其内容是一名合格的coder所必备的知识集;非科班出身的程序员要是想要有所提升,也需要认真学习这三门课程,可以快速形成计算机知识的结构体系,理解计算机底层原理,在工作实践中可以借鉴优秀的设计;而且很多互联网公司在笔试和面试中都会涉及到这三门课程的知识点,因此我通过视频学习对这三门课程就行…

    2022年4月12日
    69
  • pytorch mseloss_pytorch handbook

    pytorch mseloss_pytorch handbook1、均方损失函数:loss(xi,yi)=(xi−yi)2loss(xi,yi)=(xi−yi)2\text{loss}(\mathbf{x}_i,\mathbf{y}_i)=(\mathbf{x}_i-\mathbf{y}_i)^2这里loss,x,y的维度是一样的,可以是向量或者矩阵,i是下标。很多的loss函数都有size_average和reduc…

    2026年1月20日
    6
  • Linux-dosbox使用「建议收藏」

    2019独角兽企业重金招聘Python工程师标准>>>…

    2022年4月9日
    141
  • telnet远程登录服务器端口,telnet端口号-TELNET服务的端口号是多少?

    telnet远程登录服务器端口,telnet端口号-TELNET服务的端口号是多少?源端口是大于的随即端口 目的端口是 23 telnet 的默认端口号是多少查看端口在 windows xp server 中要查看端口 可以使用 netstat 命令 依次点击 开始 运行 键入 cmd 并回车 打开命令提示符窗口 在命令提示符状态下键入 netstat a n 按下回车键后就可以看到以数字形式显示的 tcp 和 udp 连接的端口号及状态 小知识 netstat 命令用法命令格式 netstat

    2026年3月20日
    1
  • C段与旁站

    C段与旁站旁站旁站指的是网站所在服务器上部署的其他网站旁注的意思就是从同台服务器上的其他网站入手 提权 然后把服务器端了 就自然把那个网站端了 C 段 C 段指的是例如 192 168 1 4 192 是 A 段 168 是 B 段 1 是 C 段 4 是 D 段 C 段嗅探指的是拿下同一 C 段下的服务器 也就是说是 D 段 1 255 中的一台服务器即 旁注 同服务器不同站点的渗透方案 C 段 同网段不同服务器的渗透方案

    2026年3月18日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号