V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
iambic
V2EX  ›  问与答

请问一个 rapidjson 使用问题

  •  
  •   iambic · 2015-11-21 17:01:54 +08:00 · 5606 次点击
    这是一个创建于 3291 天前的主题,其中的信息可能已经有所发展或是发生改变。

    rapidjson 是一个 c++解析 json 的库。
    使用 rapidjson 来处理 json ,发现输出和预期完全不同,以下是精简后的完整代码

    #include "rapidjson/document.h"
    #include "rapidjson/writer.h"
    #include "rapidjson/stringbuffer.h"
    #include <iostream>
    #include <string>
    
    using namespace std;
    using namespace rapidjson;
    
    void output(Document& d)
    {
        StringBuffer buffer;
        Writer<StringBuffer> writer(buffer);
        d.Accept(writer);
        cout << buffer.GetString() << endl;
    }
    int main()
    {
        const char *json = "{}";
        int i=0;
        Document d; d.Parse(json);
        Document::AllocatorType& alloc = d.GetAllocator();
    
        //注意,这里故意写成两个 block
        {
            Document d2; d2.Parse("{\"A\": null}");
            d.AddMember("0", d2, alloc);
            output(d);   //这里没有问题,输出{"0":{"A":null}}
        }
        {
            Document d2; d2.Parse("{\"B\": null}");
            d.AddMember("1", d2, alloc);
            output(d);   //这里就有问题了,输出{"0":{"B":null},"1":{"B":null}}
        }
    
    
        return 0;
    }
    

    执行后,输出如下

    {"0":{"A":null}}
    {"0":{"B":null},"1":{"B":null}}

    即第一次 AddMember 之后,顺利把 d2 作为 d["0"]的 value ,但是第二次 AddMember 时,不仅 d["1"]=d2,连原来的 d["0"]的值也发生了改变。

    如果把那两个 block 合并,这样来写

    Document d2;
        d2.Parse("{\"A\": null}");
        d.AddMember("0", d2, alloc);
        output(d);  //正常输出{"0":{"A":null}}
    
        d2.Parse("{\"B\": null}");
        d.AddMember("1", d2, alloc);
        output(d);  //正常输出{"0":{"A:null},"1":{"B":null}}
    

    那么一切正常,输出

    {"0":{"A":null}}
    {"0":{"A":null},"1":{"B":null}}

    初步看了 rapidjson 里的实现, AddMember 实际执行了一个"move"的语义,即 d.AddMember("0", d2, alloc)之后, d2 携带的内容转义给了 d, 然后 d2 自己就变成了 nulltype 了,从而合并之后那个代码能正常运行是可以理解的。

    但是还是不明白为什么写成两个 block 的代码,执行结果会是
    {"0":{"B":null},"1":{"B":null}}

    多谢。

    以下是 rapidjson 里 AddMember 的实现

    GenericValue& AddMember(GenericValue& name, GenericValue& value, Allocator& allocator) {
            RAPIDJSON_ASSERT(IsObject());
            RAPIDJSON_ASSERT(name.IsString());
    
            Object& o = data_.o;
            if (o.size >= o.capacity) {
                if (o.capacity == 0) { 
                    o.capacity = kDefaultObjectCapacity;
                    o.members = reinterpret_cast<Member*>(allocator.Malloc(o.capacity * sizeof(Member)));
                }    
                else {
                    SizeType oldCapacity = o.capacity;
                    o.capacity += (oldCapacity + 1) / 2; // grow by factor 1.5
                    o.members = reinterpret_cast<Member*>(allocator.Realloc(o.members, oldCapacity * sizeof(Member), o.capacity * sizeof(Member)));
                }    
            }    
            o.members[o.size].name.RawAssign(name);
            o.members[o.size].value.RawAssign(value);
            o.size++;
            return *this;
        } 
    
        void RawAssign(GenericValue& rhs) RAPIDJSON_NOEXCEPT {
            data_ = rhs.data_;
            flags_ = rhs.flags_;
            rhs.flags_ = kNullFlag;
        }
    
    1 条回复    2016-02-05 09:38:17 +08:00
    miloyip
        1
    miloyip  
       2016-02-05 09:38:17 +08:00
    RapidJSON 允许一个 Object 内有相同的 Key ,在 `AddMember()` 里不作检查。
    `Document` 在 consturctor 中不给与 allocator 的时候,是自行建立一个 allocator 。那么 `Parse()` 的时候会用该 allocator 来分配内存。所以两个 block 的 第一个 block 中的 `Document d2` 在离开 block 时, allocator 会连同其分配的内存同时失效。因此之后 d 里会有 dangling pointer 。
    解决方法是让 d2 采用 d 的 allocator :`Document d2(&alloc);`。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2492 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 00:28 · PVG 08:28 · LAX 16:28 · JFK 19:28
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.