这里的想法是使用INI文件为路径查找库命名的海怪实现一个配置系统。这个库是用java编写的,我正在帮助在C++中实现它。其目标是使其跨平台和具体目标的嵌入式系统。目前,INI解析器为了简单起见使用尼希,但最终应该能够在没有文件系统的情况下运行(使用INI解析器可以解析INI格式的字符串,而无需从文件中加载它)。我们还希望避免在发行版构建中使用流。
参数存储在模块中。每个模块可以在运行时更改其参数,以使路径查找适应这种情况。这是通过更改所需模块的INI部分来完成的。对于每个模块,用户可以注册在其节更改时调用的函数指针。
这些参数有硬编码的默认值,INI文件应该有一个defaut部分,其中包含所有参数值。代码应该遵循ROS风格指南。
有几件不雅的事困扰着我:
谢谢你在这方面的时间和建议,我真的很感激。
ConfigurationHandler.h
#ifndef CONFIGURATION_HANDLER_H
#define CONFIGURATION_HANDLER_H
#include <string>
#include <functional>
#include <optional>
#include "configuration_module.h"
#include "iniReader/INIReader.h"
/*
* The order of this enum is important as it gives an easy way
* to get the relation between a parameter and its module using
* the first key of each group.
* If you have to modify this enum, keep the groups in the same
* order and let the first key of each group at the first place.
*/
namespace ConfigKeys
{
enum class ConfigKeys
{
//Navmesh parameters
NavmeshObstaclesDilatation = 0,
LargestTriangleAreaInNavmesh,
LongestEdgeInNavmesh,
NavmeshFilename,
//Auto replanning
NecessaryMargin,
PreferedMargin,
MarginBeforeCollision,
InitialMargin,
//Research and mechanical parameters
MaxCurvatureDerivative,
MaxLateralAcceleration,
MaxLinearAcceleration,
DefaultMaxSpeed,
MinimalSpeed,
MaxCurvature,
StopDuration,
SearchTimeout,
ThreadNumber,
EnableDebug,
FastAndDirty,
CheckNewObstacles,
AllowBackwardMotion,
//Memory management parameters
NodeMemoryPoolSize,
ObstaclesMemoryPoolSize,
//Tentacle parameters
PrecisionTrace,
NbPoints
};
}
using ConfigKey = ConfigKeys::ConfigKeys ;
namespace ConfigModules
{
enum class ConfigModules
{
Navmesh = 0, //Require to regenerate the navmesh
Autoreplanning, //Can be modified on-the-fly
ResearchMechanical, //Can be modified on-the-fly
Memory, //Require to recreate the pools
Tentacle,
Unknown
};
}
using ConfigModule = ConfigModules::ConfigModules ;
class ConfigurationHandler
{
private:
//Structure holding all possible types of parameter value.
//It should be an union, but it will require a bit more work because of the std::string
struct ConfigurationParameter
{
double numeric_value;
bool boolean_value;
std::string string_value;
ConfigurationParameter() = default;
ConfigurationParameter(double value) { numeric_value = value; }
ConfigurationParameter(int value) { numeric_value = value; }
ConfigurationParameter(bool value) { boolean_value = value; }
ConfigurationParameter(std::string value) { string_value = value; }
};
public:
ConfigurationHandler(const std::string& filename);
void registerCallback(ConfigModule module_enum, ConfigurationCallback callback);
void changeModuleSection(ConfigModule module_enum, std::string new_section);
void changeModuleSection(std::vector<ConfigModule>&& modules, std::string new_section);
long getInt(ConfigKey key, ConfigModule module = ConfigModule::Unknown);
double getDouble(ConfigKey key, ConfigModule module = ConfigModule::Unknown);
bool getBool(ConfigKey key, ConfigModule module = ConfigModule::Unknown);
std::string getString(ConfigKey key, ConfigModule module = ConfigModule::Unknown);
private:
ConfigModule getModuleEnumFromKeyEnum(ConfigKey key) const noexcept;
std::string findSectionName(ConfigKey key, ConfigModule module_key);
void setDefaultValues();
inline std::string getKeyName(ConfigKey key);
std::optional<ConfigurationModule> getModule(ConfigModule module);
//Helper function for default values initialization
template<typename T>
void doAddDefaultValue(ConfigKey key, T value)
{
default_values_[(int)key] = ConfigurationParameter{value};
}
INIReader ini_reader_;
std::vector<ConfigurationModule> modules_;
std::vector<ConfigurationParameter> default_values_;
static constexpr int configuration_key_count = (int)ConfigKey::NbPoints + 1;
static constexpr int module_count = (int)ConfigModule::Tentacle + 1;
//The array that keeps the string values of the ConfigKeys
const std::string configuration_key_string_values[configuration_key_count] = {
"NavmeshObstaclesDilatation", "LargestTriangleAreaInNavmesh", "LongestEdgeInNavmesh", "NavmeshFilename",
"NecessaryMargin", "PreferedMargin", "MarginBeforeCollision", "InitialMargin", "MaxCurvatureDerivative",
"MaxLateralAcceleration", "MaxLinearAcceleration", "DefaultMaxSpeed", "MinimalSpeed", "MaxCurvature",
"StopDuration", "SearchTimeout", "ThreadNumber", "EnableDebug", "FastAndDirty", "CheckNewObstacles",
"AllowBackwardMotion", "NodeMemoryPoolSize", "ObstaclesMemoryPoolSize", "PrecisionTrace", "NbPoints"
};
};
#endif //CONFIGURATION_HANDLER_HConfigurationHandler.cpp
#include "configuration_handler.h"
ConfigurationHandler::ConfigurationHandler(const std::string& filename) :
ini_reader_{filename},
modules_(module_count),
default_values_(configuration_key_count)
{
setDefaultValues();
}
long ConfigurationHandler::getInt(ConfigKey key, ConfigModule module)
{
auto defaultValue = static_cast<int>(default_values_[static_cast<int>(key)].numeric_value);
auto sectionName = findSectionName(key, module);
return ini_reader_.GetInteger(sectionName, getKeyName(key), defaultValue);
}
double ConfigurationHandler::getDouble(ConfigKey key, ConfigModule module)
{
auto defaultValue = default_values_[static_cast<int>(key)].numeric_value;
auto sectionName = findSectionName(key, module);
return ini_reader_.GetReal(sectionName, getKeyName(key), defaultValue);
}
bool ConfigurationHandler::getBool(ConfigKey key, ConfigModule module)
{
auto defaultValue = default_values_[static_cast<int>(key)].boolean_value;
auto sectionName = findSectionName(key, module);
return ini_reader_.GetBoolean(sectionName, getKeyName(key), defaultValue);
}
std::string ConfigurationHandler::getString(ConfigKey key, ConfigModule module)
{
auto defaultValue = default_values_[static_cast<int>(key)].string_value;
auto sectionName = findSectionName(key, module);
return ini_reader_.Get(sectionName, getKeyName(key), defaultValue);
}
void ConfigurationHandler::registerCallback(ConfigModule module_enum, ConfigurationCallback callback)
{
auto module = getModule(module_enum);
if(module)
{
module->registerCallback(callback);
}
}
void ConfigurationHandler::changeModuleSection(ConfigModule module_enum, std::string new_section)
{
auto module = getModule(module_enum);
if(module)
{
module->changeSection(*this, new_section);
}
}
void ConfigurationHandler::changeModuleSection(std::vector<ConfigModule>&& modules, std::string new_section)
{
for(auto module : modules)
{
changeModuleSection(module, new_section);
}
}
std::string ConfigurationHandler::findSectionName(ConfigKey key, ConfigModule module_key)
{
if(module_key == ConfigModule::Unknown)
{
module_key = getModuleEnumFromKeyEnum(key);
}
return modules_[static_cast<int>(module_key)].getCurrentSection();
}
std::string ConfigurationHandler::getKeyName(ConfigKey key)
{
return configuration_key_string_values[static_cast<int>(key)];
}
ConfigModule ConfigurationHandler::getModuleEnumFromKeyEnum(ConfigKey key) const noexcept
{
//I'm not proud of this function, but I could'nt find a better solution yet.
if(key < ConfigKey::NecessaryMargin)
{
return ConfigModule::Navmesh;
}
else if(key < ConfigKey::MaxCurvatureDerivative)
{
return ConfigModule::Autoreplanning;
}
else if(key < ConfigKey::NodeMemoryPoolSize)
{
return ConfigModule::ResearchMechanical;
}
else if(key < ConfigKey::PrecisionTrace)
{
return ConfigModule::Memory;
}
else
{
return ConfigModule::Tentacle;
}
}
std::optional<ConfigurationModule> ConfigurationHandler::getModule(ConfigModule module)
{
auto moduleId = static_cast<int>(module);
if(moduleId < module_count)
{
return modules_[moduleId];
}
else
{
return std::nullopt;
}
}
void ConfigurationHandler::setDefaultValues()
{
doAddDefaultValue<int>(ConfigKey::NavmeshObstaclesDilatation, 100);
doAddDefaultValue<int>(ConfigKey::LargestTriangleAreaInNavmesh, 20000);
doAddDefaultValue<int>(ConfigKey::LongestEdgeInNavmesh, 200);
doAddDefaultValue<std::string>(ConfigKey::NavmeshFilename, "navmesh.krk");
doAddDefaultValue<int>(ConfigKey::NecessaryMargin, 40);
doAddDefaultValue<int>(ConfigKey::PreferedMargin, 60);
doAddDefaultValue<int>(ConfigKey::MarginBeforeCollision, 100);
doAddDefaultValue<int>(ConfigKey::InitialMargin, 100);
doAddDefaultValue<int>(ConfigKey::MaxCurvatureDerivative, 5);
doAddDefaultValue<int>(ConfigKey::MaxLateralAcceleration, 3);
doAddDefaultValue<int>(ConfigKey::MaxLinearAcceleration, 2);
doAddDefaultValue<int>(ConfigKey::DefaultMaxSpeed, 1);
doAddDefaultValue<int>(ConfigKey::MinimalSpeed, 0);
doAddDefaultValue<int>(ConfigKey::MaxCurvature, 5);
doAddDefaultValue<int>(ConfigKey::StopDuration, 800);
doAddDefaultValue<int>(ConfigKey::SearchTimeout, 10000);
doAddDefaultValue<int>(ConfigKey::ThreadNumber, 1);
doAddDefaultValue<bool>(ConfigKey::EnableDebug, true);
doAddDefaultValue<bool>(ConfigKey::FastAndDirty, false);
doAddDefaultValue<bool>(ConfigKey::CheckNewObstacles, false);
doAddDefaultValue<int>(ConfigKey::NodeMemoryPoolSize, 20000);
doAddDefaultValue<int>(ConfigKey::ObstaclesMemoryPoolSize, 50000);
doAddDefaultValue<bool>(ConfigKey::AllowBackwardMotion, true);
doAddDefaultValue<int>(ConfigKey::NbPoints, 5);
doAddDefaultValue<float>(ConfigKey::PrecisionTrace, 0.02f);
}ConfigurationModule.h
#ifndef CONFIGURATION_MODULE_H
#define CONFIGURATION_MODULE_H
#include <string>
#include "configuration_callback_holder.h"
class ConfigurationHandler;
class ConfigurationModule
{
public:
void registerCallback(ConfigurationCallback callback);
void changeSection(ConfigurationHandler& configuration_handler, std::string new_section);
std::string getCurrentSection();
private:
ConfigurationCallbackHolder callbacks_holder_;
std::string current_section_ = {"default"};
};
#endif //CONFIGURATION_MODULE_HConfigurationModule.cpp
#include "configuration_module.h"
void ConfigurationModule::registerCallback(ConfigurationCallback callback)
{
callbacks_holder_ += callback;
}
void ConfigurationModule::changeSection(ConfigurationHandler& configuration_handler, std::string new_section)
{
if(new_section != current_section_)
{
current_section_ = new_section;
callbacks_holder_(configuration_handler);
}
}
std::string ConfigurationModule::getCurrentSection()
{
return current_section_;
}ConfigurationCallbackHolder.h
#ifndef CONFIGURATION_CALLBACK_HOLDER_H
#define CONFIGURATION_CALLBACK_HOLDER_H
#include <vector>
#include <string>
#include <functional>
class ConfigurationHandler;
using ConfigurationCallback = std::function<void(ConfigurationHandler&)>;
class ConfigurationCallbackHolder
{
public:
void operator+=(const ConfigurationCallback callback);
void operator()(ConfigurationHandler& configuration_handler) const;
private:
std::vector<ConfigurationCallback> callbacks_;
};
#endif //CONFIGURATION_CALLBACK_HOLDER_HConfigurationCallbackHolder.cpp
#include "configuration_callback_holder.h"
void ConfigurationCallbackHolder::operator+=(const ConfigurationCallback callback)
{
callbacks_.push_back(callback);
}
void ConfigurationCallbackHolder::operator()(ConfigurationHandler& configuration_handler) const
{
auto iterator = callbacks_.cbegin();
for(; iterator != callbacks_.cend(); ++iterator)
{
(*iterator)(configuration_handler);
}
}发布于 2018-06-11 03:49:56
//The array that keeps the string values of the ConfigKeys
const std::string configuration_key_string_values[configuration_key_count] = {
"NavmeshObstaclesDilatation", "LargestTriangleAreaInNavmesh", "LongestEdgeInNavmesh", "NavmeshFilename", ⋯当我有这样的数组用于名称或其他用途(其中实际的枚举值是重要的)时,我将至少在第一个数组上使用显式初始化器(正如您所做的那样),但也会在其中解释这一点。此外,当您更改枚举时,所有必须更新的地方都将被标记为一些惟一的单词,可以对其进行标注(这是评注的一部分)。
你可能会对CppCon (née BoostCon) 2018年发布在YouTube上的短片“Enum 4 Ways”感兴趣。
枚举值需要是一个小整数的连续范围(例如,用于switch语句,存储在小单词中)还是只是作为编译时唯一的值?
因为您可以做的另一件事是定义命名常量,而不是枚举数。如果C++符号本身是一个lex字符串,那么至少在一个方向上,您会自动地保持对应关系!
struct my_enum_thing {
static const char* const NavmeshObstaclesDilatation = "NavmeshObstaclesDilatation";
// etc.符号的实际值是一个指针,并且是唯一的。但是,它们是完整的单词大小,而不是连续的数字。
您可以使用“X宏”方法从一个提供的名称列表中定义所有内容。
const std::string configuration_key_string_values[configuration_key_count] =您不需要给出数组的大小(并让前面的那些行来计算),因为它将简单地匹配初始化器计数。
您正在运行时创建所有字符串对象,这将复制字符串的字节。因此,您将在只读数据中保留原始的lex字符串,以及带有指向堆内存的指针的std::字符串,其中包含相同的内容。所有的复制都必须在程序开始时发生。
使数组本身成为一个constexpr,并使用std::string_view值。您可以在编译时构造string视图包装器,它指向lex字符串,没有重复。
using namespace std::literals::string_view_literals;
constexpr std::string_view config_values[]= {
"NavmeshObstaclesDilatation"sv, "LargestTriangleAreaInNavmesh"sv, ⋯sv在尾随收尾引号.创建字符串视图的数组而不是普通的lex字符串的优点是,代码在使用它们时不需要调用strlen。
long ConfigurationHandler::getInt(ConfigKey key, ConfigModule module)
{
auto defaultValue = static_cast<int>(default_values_[static_cast<int>(key)].numeric_value);
auto sectionName = findSectionName(key, module);
return ini_reader_.GetInteger(sectionName, getKeyName(key), defaultValue);
}
double ConfigurationHandler::getDouble(ConfigKey key, ConfigModule module)
{
auto defaultValue = default_values_[static_cast<int>(key)].numeric_value;
auto sectionName = findSectionName(key, module);
return ini_reader_.GetReal(sectionName, getKeyName(key), defaultValue);
}
bool ConfigurationHandler::getBool(ConfigKey key, ConfigModule module)
{
auto defaultValue = default_values_[static_cast<int>(key)].boolean_value;
auto sectionName = findSectionName(key, module);
return ini_reader_.GetBoolean(sectionName, getKeyName(key), defaultValue);
⋯ etc. ⋯我在这里看到了大量的重复,以及概念上应该在类型上参数化的单个操作。您应该让它成为一个模板,因此大部分代码都是自然共享的。如果ini_reader是一个第三方库,并且不以同样的方式对模板友好,那么您只需要提供一行包装,而不是复制整个身体。
x = config.get<long>(key, module);
y = config.get<double> (key2, module);
z - config.get<bool> (key3, module);哦,我的语法突出显示提醒我,module被保留为一个新的保留词。所以不要用它作为变量名!
auto module = getModule(module_enum);
if(module)
{
module->registerCallback(callback);
}成语是同时初始化和测试。其优点是,只有在正确使用的情况下,变量才在范围内!
if (auto module = getModule(module_enum))
{
module->registerCallback(callback);
}ConfigModule ConfigurationHandler::getModuleEnumFromKeyEnum(ConfigKey key) const noexcept
{
//I'm not proud of this function, but I could'nt find a better solution yet.
if(key < ConfigKey::NecessaryMargin)
{
return ConfigModule::Navmesh;
}
else if(key < ConfigKey::MaxCurvatureDerivative)
{
return ConfigModule::Autoreplanning;
}
else if(key < ConfigKey::NodeMemoryPoolSize)
{
return ConfigModule::ResearchMechanical;
}
else if(key < ConfigKey::PrecisionTrace)
{
return ConfigModule::Memory;
}
else
{
return ConfigModule::Tentacle;
}
}将值对放入数组中。所以
{ ConfigKey::NecessaryMargin, ConfigModule::Navmesh },
{ ConfigKey::MaxCurvatureDerivative, ConfigModule::Autoreplanning },
⋮然后您可以编写一次逻辑,迭代参数的元组。
for (auto [ck, cm] : the_list) {
if (key < ck) return cm;
}
// none of the above
return ConfigModule::Temtacle;这是一个普遍的概念,在这里工作可能会帮助你在更多的地方。
另一个想法,如果你可以灵活的分配代码号码,是编码的一堆高次位。所以就像
enum ⋯ { NecessaryMargin= 0, ⋯
MaxCurvatureDerivative = 0x1'0000,
MaxLateralAcceleration, // continue auto-numbering现在,您可以检查枚举值的高位数,以恢复与其匹配的模块。
if(moduleId < module_count)
{
return modules_[moduleId];
}
else
{
return std::nullopt;
}看看在调用它时生成的代码!
写它让NVRO更干净。
std::optional<ConfigurationModule> retval;
if (moduleId < module_count)
retval.emplace(modules_[moduleId]);
return retval;默认值可以在编译时生成,而不是在运行时复制到向量中。尝试在数组中按排序顺序设置它们,然后使用std::lower_bound等找到它们。顺便说一句,std::map使用了大量(动态)内存,而且速度很慢;排序向量用于查找更快!
祝你好运的项目-它看起来很有趣!
https://codereview.stackexchange.com/questions/196244
复制相似问题