Home > Software engineering >  cpp: get param from configuration file and use it in constructor
cpp: get param from configuration file and use it in constructor

Time:09-01

I'm sure this is a topic that is dealt quite a lot, but couldn't find my answer.

I'd like to construct an object based on a value parsed from a JSON file.

For example, a "class doubleBuffer" which gets its size from a configuration file.

But this object resides as a member in another class (class client), which by the time it parses the JSON, the doubleBuffer field in it is already constructed.

class doubleBuffer {
   int _size = 0;
   uint8_t _buf = nullptr;
   doubleBuffer(int size) : _size(size) {
         _buf = new (uint8_t[_size]);
   }
   doubleBuffer() {}
}

class client {
   doubleBuffer _db;
   int _size = 0;
public:
   void client() { 
      readConfiguration();  // _size is set from the configuration file
   }
   void readConfiguration() { ... }
}

int main() {
   client cl;
   // here, the configuration file is already parsed,
   // but the _db field has been constructed with a default ctor.
   // And what I'd like to happen is to construct it from the size,
   // parsed in the configuration file.
   return 0;
}

How would you recommend to solve this problem?

CodePudding user response:

Read the JSON file to a JSON object.

Pass the object as an argument to the constructor of client. Fetch branches of the json and pass them down to subobjects' constructors, like this:

class client {
    int _size;
    doubleBuffer _db;
  public:       
    client (const JSON& config) : 
          _size(config.get<int>("size")),
          _db(_size) { ... }
};

JSON config;
config.read(".config.json");
client cl(config);

(I invented the JSON class API on the fly, use whatever your library provides)

CodePudding user response:

You have 2 choices:

  • parse the JSON before you construct the client. Pass the parsed result into the client's constructor, and then let it delegate the parsed values to member constructors as needed.

  • Have client parse the JSON as you are, but then construct the doubleBuffer dynamically after the parsing is finished. Or, default-construct the doubleBuffer, and then add a method to doubleBuffer so you can resize its _buf dynamically after the JSON is parsed.

CodePudding user response:

I'd rework it a bit. You probably don't want to read the configuration every time you instantiate a client - or re-read the configuration for any other object that needs some parameter from the configuration file. Read it once and use it many times.

With most Json libraries you can also add parsers for your own types, which makes it easy to get the data out into a format that doesn't need to be parsed and re-parsed every time you need something from it. Here's an example using the Json for Modern C library.

Say your config.json looks like this:

{
    "application": "My soft config",
    "db_buf_size": 2048
}

And you want this available to your application stored in a

struct config_data {
    std::string application;
    std::size_t db_buf_size;
};

You'd simply add a from_json function to make the library able to parse it:

void from_json(const nlohmann::json& obj, config_data& c) {
    c.application = obj.at("application").get<std::string>();
    c.db_buf_size = obj.at("db_buf_size").get<std::size_t>();
}

You also need a function that you can call at any time that returns a reference to the loaded configuration and here's an example of a function named config() that returns a reference to a config_data. Since the config_data instance is static it will only read and parse the configuration from file once, no matter how many calls to config() you do:

#include <cerrno>
#include <cstring>
#include <filesystem>
#include <stdexcept>

const config_data& config() {
    static const config_data conf = [] {
        std::filesystem::path path = "config.json";
        std::ifstream ifs(path);
        if(!ifs)
            throw std::runtime_error(path.string()   ": "  
                                     std::strerror(errno));
        auto j = json::parse(ifs);
        return j.get<config_data>(); // using the user defined parser
    }();

    return conf;
}

You can now let the client constructor call config() and fetch any value from the configuration. Again, since the actual config_data instance in the config() function is static, actually loading and parsing the data will only done once.

class doubleBuffer {
public:
    doubleBuffer(size_t size) : m_size(size), m_buf{new uint8_t[m_size]} {}

    doubleBuffer(const doubleBuffer&) = delete;
    doubleBuffer(doubleBuffer&&) = delete;
    ~doubleBuffer() { delete[] m_buf; }

    size_t size() const { return m_size; }

private:
    size_t m_size = 0;
    uint8_t* m_buf = nullptr;
};

class client {
public:
    client() : m_db(config().db_buf_size) {}  // fetch the needed data

    size_t size() const { return m_db.size(); }

private:
    doubleBuffer m_db;
};

You may also want to consider using a std::vector<uint8_t> instead of a raw uint8_t*.

  • Related