应用程序/程序设置的C ++类?

时间:2011-06-16 22:11:12

标签: c++ visual-c++ configuration mfc settings

将Visual Studio C ++与MFC一起使用。我想弄清楚什么是存储应用程序/程序设置的好方法。我不是指他们的持久存储,而是指代代码中用来保存设置的数据结构。

我创建了一个名为Settings的静态类,它有几个静态方法,还有嵌套类来对设置进行分区。例如:

class Settings
{
public:
    Settings(void);
    ~Settings(void);

    static void SetConfigFile(const char * path);
    static CString GetConfigFilePath();
    static void Load();
    static void Save();

    class General
    {
    public:
        static CString GetHomePage();
        static void SetHomePage(const char * url);
    private:
        static int homePageUrl_;
    };

private:
    static CString configFilePath_;
};

然后我可以在我的代码中访问我的设置,例如:

Settings::General::GetHomePage();

现在我进入单元测试,我开始意识到静态类是不受欢迎的。所以我想把它变成一个基于实例的类。但是我必须管理嵌套的类实例,这些实例虽然微不足道,但对于测试来说仍然有点麻烦。嵌套类的全部意图只是将设置分组为逻辑组。我辩论基于字符串的设置类是否会更好,像设置 - >获得(“General.HomePage”),但我想我更喜欢的专用访问方法的强类型

所以,让我的问题是什么是一个很好的数据结构来保存程序配置/设置,支持简单的单元测试?

3 个答案:

答案 0 :(得分:4)

如果适合您,您可以这样做。您可以抛弃枚举并转到const字符串甚至是自由形式的字符串。枚举也不一定要在类中定义。有很多方法可以做到。

如果你想实现类别,另一个类可以使用模板来定义枚举类型,从而对多个实例做类似的事情。

只是一个想法。

#include "stdafx.h"
#include <map>
#include <string>
#include <iostream>
using namespace std;


class Settings
{
public:

  typedef enum
  {
    HomePageURL,
    EmailAddress,
    CellPhone
  } SettingName;

private:
  typedef map<SettingName, string> SettingCollection;

  SettingCollection theSettings;


public:

  string& operator[](const SettingName& theName)
  {
    return theSettings[theName];
  }

  void Load ()
  {
    theSettings[HomePageURL] = "http://localhost";
  }

  void Save ()
  {
    // Do whatever here
  }

};


int _tmain(int argc, _TCHAR* argv[])
{
  Settings someSettings;

  someSettings.Load ();

  cout << someSettings [Settings::SettingName::HomePageURL] << endl;


    return 0;
}

答案 1 :(得分:2)

我认为您的要求之间不存在冲突:(1)提供对配置变量的类型安全访问; (2)使用"fully.scoped.name"语法指定配置变量的名称。当然,您可以进行类型安全的操作,例如:

const char * getString(const char * fullyScopedName);
int          getInt(const char * fullyScopedName);
bool         getBool(const char * fullyScopedName);

您可以通过阅读我的PDF库的入门指南HTMLConfig4Cpp)的第2章和第3章找到一些灵感。

编辑:我提到的Config4Cpp文档可能为API设计提供了灵感,但我迟到意识到您可能会欣赏有关实现选项的建议,以防您决定从头开始编写自己的配置类(而不是而不是像Config4Cpp那样使用第三方库...

您的班级应使用std::map来存储 fullyScopedName-&gt;值映射的集合。显然, fullyScopedName 将是一个字符串,但有两个选项可用于表示

第一个选项是将表示为字符串。类型安全的访问器(例如getInt()getBool())将从映射中检索基于字符串的值,然后解析它以将其转换为所需的类型。如果解析失败,则访问者操作会引发异常。 (这是Config4Cpp采用的方法。)

第二个选项是表示 value ,如下面的伪代码所示:

enum ValueType { STRING_VAL, INT_VAL, BOOL_VAL };
struct Value {
    ValueType         type;
    union {
        const char *  stringVal;
        int           intVal;
        bool          boolVal;
    } data;
};

然后可以按如下方式编码类型安全访问器的实现(伪代码):

int getInt(const char * fullyScopedName)
{
    Value * v = nameValueMap[fullyScopedName];
    if (v->type != INT_VAL) {
        throw WrongTypeException(...);
    }
    return v->data.intVal;
}

答案 2 :(得分:1)

这是我现在使用的课程,主要受到Nathan的答案的启发,除了模板化的方法:

class Settings {
public:
    Settings(void);
    virtual ~Settings(void);

    enum SettingName { General_WindowWidth, General_HomePageUrl,
        General_ShowDownloadsWindow, Privacy_RememberPasswords,
        Privacy_RememberHistory };

    virtual void SetConfigFile(const char * path);
    virtual std::string GetConfigFilePath();
    virtual void Load();
    virtual void Save();

    template<class T>
    T Get(SettingName name) {
        return boost::lexical_cast<T>(settings_[name]);
    }

    template<class T>
    void Set(SettingName name, T value) {
        settings_[name] = boost::lexical_cast<std::string>(value);
    }

    void Set(SettingName name, std::string value) {
        settings_[name] = value;
    }

private:
    std::string configFilePath_;
    std::map<SettingName, std::string> settings_;
};