用于记录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 <iostream>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; // 输出为value1value1 = "abc";v = doc["key1"].GetString();cout << v.c_str() << endl; // 输出为abcsystem("pause");return 0;
}
输出结果证明,上述操作为字符串浅拷贝。所以如果有下面这样的代码,输出将是不确定的值。
void addMem(Document& doc) {string value = "123456";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<typename CharType>
inline GenericStringRef<CharType> StringRef(const CharType* str, size_t length) {return GenericStringRef<CharType>(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 = "123456";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相同。于是很开心的看是编译运行,然而得到的结果却兵不正确。
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的时候要格外当心。