Skip to content

Q: few questions about json construction #1092

Description

@crusader-mike

I was digging in json.hpp (v2.1.0) in order to understand how stuff gets created and check for unnecessary copies/allocations in simple cases like:

json x = { {"val1", 1}, {"val2", true} };

As I see it:

  • basic_json has expensive copy semantics as evidenced by it's cctor and op=:
    basic_json(const basic_json& other)
        : m_type(other.m_type)
    {
        ...
        switch (m_type)
        {
            case value_t::object:
            {
                m_value = *other.m_value.object;   <-- mem alloc + cctor call
                break;
            }
        ...

    reference& operator=(basic_json other) ...    <-- note lack of const&
    {
        ...
        using std::swap;
        swap(m_type, other.m_type);
        swap(m_value, other.m_value);
  • which means in my example we will end up copying the same data many times (depending how deep in hierarchy given piece of data is)

  • {"val1", 1} will be used to create a basic_json of type array that contains two basic_json objects of types string and whatever respectively (underlying vector will be allocated on heap, same for that string)

  • then it will be used to populate an std::map entry in basic_json object of type object here:

    basic_json(std::initializer_list<basic_json> init,
               bool type_deduction = true,
               value_t manual_type = value_t::array)
    {
        ...
            std::for_each(init.begin(), init.end(), [this](const basic_json & element)
            {
                m_value.object->emplace(*(element[0].m_value.string), element[1]);
            });

where m_value.object is of type std::map<string, basic_json> and emplace will end up passing a string& to related constructor (which will have to make another copy of a string)

  • and at some point initializer_list instances will be destroyed causing destruction of related basic_json objects and aforementioned vector and string objects will be deallocated

Please, correct me if this is incorrect or I missed smth important.

Now, as I see it there is a quick and easy way to avoid few copies:

m_value.object->emplace(std::move(*(element[0].m_value.string)), std::move(element[1]));

Q1: do you think it is ok? if not -- why?

Q2: this process has a lot of extra allocations (basic_json copies, underlying vectors and strings) -- is there an approach to construct my json object and avoid them?

Q3: pushing it further a bit -- inserting new sub-element into object means std::map lookup, can I avoid it somehow? For example by providing sub-elements in proper order and forcing related std::map::emplace/insert to use end() as a hint.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions