c++boostboost-propertytree

Recursively adding subtrees to boost property tree


I want to write a parameter server in C++ where I can recursively dump a tree of parameters into a property tree and then write it to a JSON file. The dump function looks like this:

void Params::dump(string filename) {
    // Create a root
    pt::ptree root;
 
    // Fill the root with parameters
    mapToPt(curParams, root);
 
   // Write to count
   pt::write_json(cout, root);
}

mapToPt is supposed to recursively go through the hierarchy of my parameter server and fill the property tree while doing so:

void Params::mapToPt(boost::shared_ptr<Params> curParams, pt::ptree &root) {
    // Fill current root with parameters from curParams ParameterMap
    map<string, boost::shared_ptr<Param>>::iterator it;
    for (it = curParams->getParamMap().begin(); it != curParams-getParamMap().end(); it++) {
        root.put(it->first, it->second->getValue());
        cout << "Add Parameter: \n";
        cout << "Parameter name: " << it->first << "\n";
        cout << "Parameter value: " << it->second->getValue() << "\n";
    }
 
    // Recursively go through all children to do the same to them
    if(curParams->hasChildren()) { //ERROR LINE
        map<string, boost::shared_ptr<Params> >::iterator it;
        for(it = curParams->getChildren().begin(); it != curParams-getChildren().end(); it++) {
            pt::ptree new_tree;
            root.add_child(it->second->getName(), new_tree);
            cout << "Add Child: \n";
            cout << "Child name: " << it->second->getName() << "\n";
            mapToPt(it->second, new_tree);
        }
    }
}

My problem is that as soon as I go into recursion, errors occur at random lines that can not be the cause of the error. "basic_string::_M_construct null not valid" is the error message. I believe that I might access deleted content and that it might be due to the way I iterate through the property tree children. Is my way of doing that wrong or is there another way to do it?

Thank you.


Solution

  • Why is mapToPt a member when it also expects a pointer to a Params instance?

    Anyhoops, there's quite a bit of confusion.

    At a design level, your Params type looks like it cannot decide whether it's a leaf node or not. Moreover, it suffers from "Quasi Classes" design, where getters essentially guarantee that there is no class invariant possible. In such cases, prefer to just have a struct with member fields.

    Note, if you fail to return by reference from getParamMap() and getChildren() then you already have Undefined Behaviour in both loops, because the iterators then point into non-existent copies of containers.

    You should check this. Also, see my working demo below

    At the implementation level, this is causing you problems:

    pt::ptree new_tree;
    root.add_child(it->second->getName(), new_tree);
    

    add_child inserts a copy of new_tree. Any future modification to new_tree has no effect. Instead, write:

    pt::ptree& new_tree = root.add_child(it->second->getName(), {});
    

    Here, new_tree becomes a reference to the actually added tree.

    Attempted Fix

    The style is still below my expectations. Personally, I'd closely review the use of shared_ptr at all in this piece of code.

    But this will probably help you along:

    Live On Coliru

    #include <boost/make_shared.hpp>
    #include <boost/property_tree/json_parser.hpp>
    #include <iostream>
    #include <map>
    
    namespace pt = boost::property_tree;
    
    struct Param {
        std::string getValue() const { return "42"; }
    };
    
    struct Params {
        using ParamMap = std::map<std::string, boost::shared_ptr<Param> >;
        using Children = std::map<std::string, boost::shared_ptr<Params> >;
    
        Params(std::string name = "") : _name(name) {}
    
        std::string getName() const         { return _name; }
    
        ParamMap& getParamMap()             { return _map; } 
        ParamMap const& getParamMap() const { return _map; } 
    
        bool hasChildren() const            { return !_children.empty(); }
        Children& getChildren()             { return _children; } 
        Children const& getChildren() const { return _children; } 
    
        static void mapToPt(boost::shared_ptr<Params> curParams, pt::ptree &root);
    
      private:
        std::string _name;
        ParamMap _map;
        Children _children;
    };
    
    void Params::mapToPt(boost::shared_ptr<Params> curParams, pt::ptree &root) {
        // Fill current root with parameters from curParams ParameterMap
        std::map<std::string, boost::shared_ptr<Param> >::iterator it;
        for (it = curParams->getParamMap().begin(); it != curParams->getParamMap().end(); it++) {
            root.put(it->first, it->second->getValue());
            //std::cout << "Add Parameter: \n";
            //std::cout << "Parameter name: " << it->first << "\n";
            //std::cout << "Parameter value: " << it->second->getValue() << "\n";
        }
    
        // Recursively go through all children to do the same to them
        if (curParams->hasChildren()) {
            for (auto it = curParams->getChildren().begin(); it != curParams->getChildren().end(); it++) {
                pt::ptree& new_tree = root.add_child(it->second->getName(), {});
                //std::cout << "Add Child: \n";
                //std::cout << "Child name: " << it->second->getName() << "\n";
                mapToPt(it->second, new_tree);
            }
        }
    }
    
    int main() {
        auto a = boost::make_shared<Params>("rootparams");
    
        a->getParamMap().emplace("one", boost::make_shared<Param>());
        a->getParamMap().emplace("two", boost::make_shared<Param>());
        a->getParamMap().emplace("three", boost::make_shared<Param>());
    
        a->getChildren().emplace("child1", boost::make_shared<Params>("child1-name"))
            .first->second->getParamMap().emplace("four", boost::make_shared<Param>());
    
        a->getChildren().emplace("child2", boost::make_shared<Params>("child2-name"))
            .first->second->getParamMap().emplace("five", boost::make_shared<Param>());
    
        pt::ptree root;
        a->mapToPt(a, root);
    
        write_json(std::cout, root);
    }
    

    Prints

    {
        "one": "42",
        "three": "42",
        "two": "42",
        "child1-name": {
            "four": "42"
        },
        "child2-name": {
            "five": "42"
        }
    }