RapidJson踩坑记录

用于记录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 = "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
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 = "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的时候要格外当心。

你可能感兴趣的:(杂)