首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >配置管理器

配置管理器
EN

Code Review用户
提问于 2018-06-10 21:41:15
回答 1查看 787关注 0票数 1

这里的想法是使用INI文件为路径查找库命名的海怪实现一个配置系统。这个库是用java编写的,我正在帮助在C++中实现它。其目标是使其跨平台和具体目标的嵌入式系统。目前,INI解析器为了简单起见使用尼希,但最终应该能够在没有文件系统的情况下运行(使用INI解析器可以解析INI格式的字符串,而无需从文件中加载它)。我们还希望避免在发行版构建中使用流。

参数存储在模块中。每个模块可以在运行时更改其参数,以使路径查找适应这种情况。这是通过更改所需模块的INI部分来完成的。对于每个模块,用户可以注册在其节更改时调用的函数指针。

这些参数有硬编码的默认值,INI文件应该有一个defaut部分,其中包含所有参数值。代码应该遵循ROS风格指南

有几件不雅的事困扰着我:

  • 枚举到字符串的转换。
  • 查找与特定ConfigKey相关的模块
  • ConfigurationParameter结构和硬编码默认值的使用(这里只有一部分代码才能工作)

谢谢你在这方面的时间和建议,我真的很感激。

ConfigurationHandler.h

代码语言:javascript
复制
#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_H

ConfigurationHandler.cpp

代码语言:javascript
复制
#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

代码语言:javascript
复制
#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_H

ConfigurationModule.cpp

代码语言:javascript
复制
#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

代码语言:javascript
复制
#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_H

ConfigurationCallbackHolder.cpp

代码语言:javascript
复制
#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);
    }
}
EN

回答 1

Code Review用户

回答已采纳

发布于 2018-06-11 03:49:56

代码语言:javascript
复制
//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字符串,那么至少在一个方向上,您会自动地保持对应关系!

代码语言:javascript
复制
 struct my_enum_thing {
     static const char* const NavmeshObstaclesDilatation = "NavmeshObstaclesDilatation";
     // etc.

符号的实际值是一个指针,并且是唯一的。但是,它们是完整的单词大小,而不是连续的数字。

您可以使用“X宏”方法从一个提供的名称列表中定义所有内容。

代码语言:javascript
复制
const std::string configuration_key_string_values[configuration_key_count] =

您不需要给出数组的大小(并让前面的那些行来计算),因为它将简单地匹配初始化器计数。

您正在运行时创建所有字符串对象,这将复制字符串的字节。因此,您将在只读数据中保留原始的lex字符串,以及带有指向堆内存的指针的std::字符串,其中包含相同的内容。所有的复制都必须在程序开始时发生。

使数组本身成为一个constexpr,并使用std::string_view值。您可以在编译时构造string视图包装器,它指向lex字符串,没有重复。

代码语言:javascript
复制
using namespace std::literals::string_view_literals;

constexpr std::string_view config_values[]= {
    "NavmeshObstaclesDilatation"sv, "LargestTriangleAreaInNavmesh"sv, ⋯

注意到sv在尾随收尾引号.

创建字符串视图的数组而不是普通的lex字符串的优点是,代码在使用它们时不需要调用strlen

代码语言:javascript
复制
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是一个第三方库,并且不以同样的方式对模板友好,那么您只需要提供一行包装,而不是复制整个身体。

代码语言:javascript
复制
x = config.get<long>(key, module);
y = config.get<double> (key2, module);
z - config.get<bool> (key3, module);

哦,我的语法突出显示提醒我,module被保留为一个新的保留词。所以不要用它作为变量名!

代码语言:javascript
复制
auto module = getModule(module_enum);
if(module)
{
    module->registerCallback(callback);
}

成语是同时初始化和测试。其优点是,只有在正确使用的情况下,变量才在范围内!

代码语言:javascript
复制
if (auto module = getModule(module_enum))
{
    module->registerCallback(callback);
}
代码语言:javascript
复制
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;
    }
}

将值对放入数组中。所以

代码语言:javascript
复制
{ ConfigKey::NecessaryMargin, ConfigModule::Navmesh },
{ ConfigKey::MaxCurvatureDerivative, ConfigModule::Autoreplanning },
    ⋮

然后您可以编写一次逻辑,迭代参数的元组。

代码语言:javascript
复制
for (auto [ck, cm] : the_list) {
    if (key < ck)  return cm;
}
// none of the above
return ConfigModule::Temtacle;

这是一个普遍的概念,在这里工作可能会帮助你在更多的地方。

另一个想法,如果你可以灵活的分配代码号码,是编码的一堆高次位。所以就像

代码语言:javascript
复制
enum ⋯ { NecessaryMargin= 0, ⋯
         MaxCurvatureDerivative = 0x1'0000,
         MaxLateralAcceleration, // continue auto-numbering

现在,您可以检查枚举值的高位数,以恢复与其匹配的模块。

代码语言:javascript
复制
    if(moduleId < module_count)
    {
        return modules_[moduleId];
    }
    else
    {
        return std::nullopt;
    }

看看在调用它时生成的代码!

写它让NVRO更干净。

代码语言:javascript
复制
std::optional<ConfigurationModule> retval;
if (moduleId < module_count)
    retval.emplace(modules_[moduleId]);
return retval;

默认值可以在编译时生成,而不是在运行时复制到向量中。尝试在数组中按排序顺序设置它们,然后使用std::lower_bound等找到它们。顺便说一句,std::map使用了大量(动态)内存,而且速度很慢;排序向量用于查找更快!

祝你好运的项目-它看起来很有趣!

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/196244

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档