Как хранить данные в boost::program_options::variable_map?

#c #boost #boost-program-options

#c #boost #boost-program-options

Вопрос:

В настоящее время я пытаюсь переработать некоторый код, который был передан мне. Исходный пункт кода — прочитать файл конфигурации и настроить различные параметры в файле в boost::program_options::variable_map, который затем считывается в других частях кода, который уже работает нормально.

Вот код, который я пытаюсь заменить:

 // Some helpful definitions
boost::program_options::variables_map vm;
std::string filecfg = "File_name";
std::ifstream ifs(filecfg.c_str());

// Setting options (This is command line example, but config file is the same)
boost::program_options::options_description df("Command Line");
df.add_options()
    ("help", "display help message")
    ("file-cfg,f", boost::program_options::value<std::string>(), "config file")
    ("version", "display version and revision number");

boost::program_options::parsed_options parsedc = boost::program_options::parse_config_file(ifs, df, true);
boost::program_options::store(parsedc, vm);
boost::program_options::notify(vm);
std::vector <std::string> unrc = boost::program_options::collect_unrecognized(parsedc.options, boost::program_options::include_positional)
  

Я думаю, что это просто заменить boost::program_options::parsed_options parsedc и создать этот объект самостоятельно. Проблема, с которой я сталкиваюсь, заключается просто в том, что нет документации о том, как это сделать. Я думаю, это в основном потому, что он не предназначен для использования таким образом.

В любом случае, я просто хочу заполнить объект виртуальной машины параметрами, описанными в dc, и значениями, которые я могу хранить в отдельной структуре данных (например, в векторе).

Возможно ли просто добавить значения в vm? Или я должен пройти через такую функцию, как boost::program_options::store()?

Любая помощь была бы с благодарностью! Дайте мне знать, если что-то неясно, или если есть что-то, что вы хотели бы, чтобы я попробовал!

Спасибо!

Ответ №1:

Да, вы можете.

Имейте в виду, что вам придется решить, как «издеваться» / «подделывать» другую его семантику. (Например, вы можете захотеть замаскировать параметры как значения по умолчанию)

Концептуально variable_map был бы map<string, variable_value> . variable_value :

Класс, содержащий значение параметра. Содержит подробную информацию о том, как устанавливается значение, и позволяет удобно получать значение.

Обратите также внимание, что из-за variable_value использования boost::any для хранения вам нужно будет быть точным в отношении типов, которые вы будете хранить. (Так что не сохраняйте, "oops" если вам нужно std::string("ah okay") ).

Вот простая демонстрация:

Жить на Coliru

 #include <boost/program_options.hpp>
#include <iostream>
#include <iomanip>

namespace po = boost::program_options;
using namespace std::string_literals;

int main(/*int argc, char** argv*/) {
    // Some helpful definitions
    po::variables_map vm;

    vm.emplace("file-cfg", po::variable_value("string"s, true));
    vm.emplace("log-level", po::variable_value(3, false));
    vm.emplace("option3", po::variable_value{});
    notify(vm);

    std::vector unrc = { "unrecognized"s, "options"s };

    for (autoamp; [name, value] : vm) {
        std::cout
            << "Name: " << name
            << std::boolalpha
            << "tdefaulted:" << value.defaulted()
            << "tempty:" << value.empty();

        if (typeid(std::string) == value.value().type())
            std::cout << " - string " << std::quoted(value.as<std::string>()) << "n";
        else if (typeid(int) == value.value().type())
            std::cout << " - int " << value.as<int>() << "n";
        else if (!value.empty())
            std::cout << " - unknown typen";
    }
}
  

С принтами

 Name: file-cfg  defaulted:true  empty:false - string "string"
Name: log-level defaulted:false empty:false - int 3
Name: option3   defaulted:false empty:true
  

Ответ №2:

Я предупреждаю вас не использовать vm.emplace(…, po::variable_value(…,…)) .
Это довольно обманчиво: в какой-то степени это сработает, но в другом месте произойдет впечатляющий сбой.

Когда вы используете po::store , он внутренне также создает po::variable_value и копирует семантику вашего параметра в частное поле po::variable_value . Нет способа установить эту семантику самостоятельно.
(см.: https://github.com/boostorg/program_options/blob/develop/src/variables_map.cpp#L83).

Без семантики вы не можете, по крайней мере:

  • прочитайте параметр из нескольких источников
  • vm.notify() не будет записывать значение в переменные, связанные с параметром

Вот (возможно, уродливый) способ, который должен избежать этих проблем:

 po::variables_map vm;
po::options_description opts;
opts.add_options()("optName", …);
…
po::parsed_options parsedOpts(amp;opts);
pOpts.options.push_back(po::option("optName", {"optValue"}));
po::store(parsedOpts, vm);
  

Сначала нужно создать parsedOpts объект, который хранит описание опций. Затем добавьте имя параметра и список значений в виде строк, независимо от типа параметра. Наконец, внутри po::store имя и значения анализируются и сохраняются в vm .


Полный рабочий пример:

 #include <boost/program_options.hpp>
#include <iostream>
namespace po = boost::program_options;

int main(int ac, char **av) {
    int i = 0;
    po::variables_map vm;
    po::options_description opts;
    opts.add_options()("id", po::value<int>(amp;i)->default_value(1));

    int userInput;
    std::cin >> userInput;

    if (userInput == 2) {
        vm.emplace("id", po::variable_value(userInput, false));
    }

    if (userInput == 3) {
        po::parsed_options pOpts(amp;opts);
        pOpts.options.push_back(po::option("id", {std::to_string(userInput)}));
        po::store(pOpts, vm);
    }

    po::store(po::parse_command_line(ac, av, opts), vm);

    //po::store(po::parse_config_file("options.ini", opts, true), vm);

    po::notify(vm);

    std::cout << "id (notified): " << i << std::endl;
    std::cout << "id (from vm):  " << vm["id"].as<int>() << std::endl;
    return 0;
}
  

Ввод 2 выполняется vm.emplace и выдает:

 id (notified): 0
id (from vm):  2
  

что плохо, так как po::value<int>(amp;i) было проигнорировано

Ввод 2 и добавление --id=4 аргумента командной строки приводит:

 terminate called after throwing an instance of 'boost::wrapexcept<boost::program_options::multiple_occurrences>'
  

что плохо, поскольку вы не можете использовать несколько источников

Ввод 3 запускает po::store вручную po::parsed_options и выдает:

 id (notified): 3
id (from vm):  3
  

Ввод 3 и добавление --id=4 аргумента командной строки приводит:

 id (notified): 3
id (from vm):  3
  

Что является правильным и ожидаемым, для последующих хранилищ не должно заменять значения, которые были сохранены ранее
(см. https://www.boost.org/doc/libs/1_80_0/doc/html/program_options/tutorial.html#id-1.3.30.4.5)