Я наткнулся на проблему, определенную в названии. У меня есть приложение, которое создает экземпляр options_description
затем использует add_options()
в теме.
Совсем как в примере:
options_description desc;
desc.add_options()
("help", "produce help")
("optimization", value<int>()->default_value(10), "optimization level")
;
У меня вопрос, как можно изменить значение по умолчанию для optimization
после этого звонка. Это вообще возможно? Документация кажется мне довольно мутной. Из того, что я понимаю, вопрос можно обобщить на любое значение семантического, как value_semantic
является вторым параметром в скобках.
мотивация
У меня такое чувство, что это может быть невозможно. Поэтому я хотел бы представить свою мотивацию для такой функциональности. Может быть, мой намеренный дизайн имеет недостатки, так что вы можете предложить что-то еще.
У меня есть несколько программ, которые выполняют довольно похожие задачи и имеют несколько общих параметров и параметров. Я думал, что смогу реорганизовать общие параметры в отдельный базовый класс. Я подумал, что могу реорганизовать разбор командной строки таким же образом. boost::program_options
работаю довольно хорошо с моей идеей. Я строю options_description
Экземпляр как частное свойство в базовом классе и добавить общие параметры там. Затем в производных классах при инициализации я выполняю add_options()
на этот объект снова добавляются более конкретные параметры. Это казалось довольно опрятным, и я получил его довольно быстро.
Затем я заметил, что все производные классы будут иметь общую опцию, но было бы неплохо иметь другое значение по умолчанию. То есть имя выходного файла. Для app1 должно быть app1.out, app2 — app2.out и т. Д.
Конечно, я могу переместить параметр выходного файла в add_options
в производных классах, но это кажется глупым и избыточным, так как даже семантически все одинаково, кроме значения по умолчанию. Другим обходным решением было бы отказаться от значения по умолчанию в базовом классе и на этапе после анализа в производных классах проверить, была ли установлена опция, и применить (по умолчанию) значение вручную. Однако это также кажется избыточным, поскольку предполагаемая функциональность, кажется, реализована в самой библиотеке.
Я постараюсь предоставить пример кода, чтобы вы могли почувствовать его позже или по запросу. Хотя я думаю, что мой подход довольно ясен.
РЕДАКТИРОВАТЬ — Примеры кода
Это было написано после ответа Роба, поэтому я старался придерживаться соглашения об именах.
Base — выполняет синтаксический анализ и позволяет задать уровень оптимизации как целое число:
#include <boost/program_options.hpp>
namespace po = boost::program_options;
class BaseClass {
public:
BaseClass::BaseClass();
virtual int parse(const int argc, char** argv);
private:
po::options_description m_desc;
po::variables_map vm;
int optimization_level;
};
BaseClass::BaseClass():
m_desc()
{
m_desc.add_options()
("help", "produce help")
("optimization", value<int>()->default_value(10), "optimization level")
;
}
int BaseClass::parse(const int argc, char** argv)
{
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
if (vm.count("help")) { std::cout << desc << "\n"; return 1; }
optimization_level = vm["optimization"].as<int>();
return 0;
}
Высокооптимизированная версия, которая позволяет дополнительно выполнять модные вещи:
class HighlyOptimizedClass : public BaseClass {
public:
HighlyOptimizedClass();
virtual int parse(const int argc, char** argv);
private:
bool fancy_optimizations;
};
HighlyOptimizedClass(): BaseClass() {
m_desc.add_options()
("fancy,f", po::value<bool>()->zero_tokens(), "perform fancy optimizations")
;
}
HighlyOptimizedClass::parse(const int argc, char** argv)
{
int ret = BaseClass::parse(argc, argv); //execute base function
if( ret ) return ret; //return if it didnt succed
if ( vm.count("fancy") ) fancy_optimizations = 1; // non-base stuff
return 0;
}
Неоптимизированная версия, которая позволяет включить подробную отладку:
class NonOptimizedClass : public BaseClass {
public:
NonOptimizedClass();
virtual int parse(const int argc, char** argv);
private:
bool verbose_debug;
};
NonOptimizedClass(): BaseClass() {
m_desc.add_options()
("verbose,v", po::value<bool>()->zero_tokens(), "genrates TONS of output")
;
}
NonOptimizedClass::parse(const int argc, char** argv)
{
int ret = BaseClass::parse(argc, argv); // execute base function
if( ret ) return ret; // return if it didnt succed
if ( vm.count("verbose") ) verbose_debug = 1; // non-base stuff
return 0;
}
Я пытался сжать его вертикально, но в любом случае он стал длинным = /. Извините, если я пошел за борт. В свою очередь, примеры понятны и самодостаточны.
BaseClass
настраивает почти все и разбирает обычные вещи. Производные классы добавляют свои собственные опции в конструкторы и анализируют перегрузку. Они выполняют базовый анализатор и проверяют наличие ошибок. Это также делает --help
Работа.
Теперь дело в том, чтобы изменить значение оптимизации по умолчанию для каждого из производных. Как было бы неплохо, чтобы оно было установлено очень низким для NonOptimizedClass
и действительно высоко для OptimizedClass
,
Ты можешь позвонить options_description::find("optimization", ...)
чтобы получить ссылку на связанный option_description
, И его semantic
метод даст вам указатель на value_semantic
что вы изначально предоставили во время звонка add_options
, Однако это константный указатель, поэтому вам не разрешено изменять то, на что он указывает.
Тем не менее value_semantic
не был const, когда вы его создали, а это значит, что он должен быть безопасным в использовании const_cast
удалить постоянную квалификацию, которая option_description
применяется. Вам также придется набрать value_semantic
Возврат вправо typed_value
типа, что вы получили, когда вы первоначально позвонили value<T>
,
option_description const& optimization = desc.find("optimization", false);
shared_ptr<const value_semantic> cvalue = optimization.semantic();
shared_ptr<value_semantic> value = const_pointer_cast<value_semantic>(cvalue);
shared_ptr<typed_value<int>> tvalue = dynamic_pointer_cast<typed_value<int>>(value);
assert(tvalue);
tvalue->default_value(20);
Альтернативный дизайн, позволяющий избежать необходимости изменять параметры после их определения (что явно не является чем-то program_options
было разработано, чтобы делать), чтобы программный производный класс передавал желаемое значение по умолчанию в базовый класс. Тогда базовый класс может использовать это значение при определении опции оптимизации.
BaseClass::BaseClass(int default_optimization):
m_desc()
{
m_desc.add_options()
("help",
"produce help")
("optimization",
value<int>()->default_value(default_optimization),
"optimization level")
;
}
HighlyOptimizedClass::HighlyOptimizedClass():
BaseClass(99)
{ }
NonOptimizedClass::NonOptimizedClass():
BaseClass(0)
{ }
const std::vector< shared_ptr< option_description > > & options_description::options() const;
дает вам право на запись в option_description
,
const shared_ptr<T>
это не shared_ptr<const T>
,
Однако я бы вместо этого использовал add_output_file( std::string default )
и позвони add_option
Если бы у меня была такая проблема. (вполне возможно, несмотря на то, что вышеприведенное выглядит правдоподобно для интерфейса, путаница с внутренними компонентами может запутать boost
библиотека.
Итак, я понял, что в текущей версии невозможно изменить value_semantic
без нарушения существующего дизайна program_options
от const_cast
ING.
Прочитав ответ и предложения Роба Кеннеди и Якка, я придумал подход, который является комбинацией двух. Он должен работать так, как описано, и не должен ничего загромождать без необходимости.
Идея состоит в том, чтобы добавить опцию, которая предназначена для изменения в отдельном вызове. Сделайте его виртуальным и определите регистр по умолчанию в базовом классе.
Такой подход позволяет настроить весь program_option
сразу, а не только единый смысл. Добавление параметра или метода для каждого отдельного случая, который может быть изменен, кажется мне очень обременительным.
Измененные коды будут выглядеть так:
База
class BaseClass {
public:
BaseClass::BaseClass();
virtual int parse(const int argc, char** argv);
private:
virtual void add_optimization_option();
po::options_description m_desc;
po::variables_map vm;
int optimization_level;
};
BaseClass::BaseClass(): m_desc() {
m_desc.add_options()("help", "produce help");
}
void BaseClass::add_optimization_option(){
m_desc.add_options()
("optimization", value<int>()->default_value(10), "optimization level");
}
Оптимизированная версия:
class HighlyOptimizedClass : public BaseClass {
public:
HighlyOptimizedClass();
virtual int parse(const int argc, char** argv);
private:
virtual void add_optimization_option();
bool fancy_optimizations;
};
void HighlyOptimizedClass::add_optimization_option(){
m_desc.add_options()
("optimization", value<int>()->default_value(99), "optimization level");
}
Неоптимизированные:
class NonOptimizedClass : public BaseClass {
public:
NonOptimizedClass();
virtual int parse(const int argc, char** argv);
private:
virtual void add_optimization_option();
bool verbose_debug;
};
void NonOptimizedClass::add_optimization_option(){
m_desc.add_options()
("optimization", value<int>()->default_value(0), "optimization level");
}
Стоимость состоит в том, чтобы добавить один закрытый метод для каждой измененной опции и перегрузить один закрытый метод для каждого случая, когда мы хотим изменить его. Когда кто-то хочет оставить его по умолчанию, ничего не нужно.
Если модификация value_semantic
было возможно определить новые методы можно было избежать. Тем не менее, кроме этого препятствия, оно работает хорошо и не загромождает ничего другого.