2

我正在尝试编写一个通用设置管理器。设置来自 INI 文件,可以是整数或字符串。我希望能够存储这些设置的向量,因此我可以遍历它们以找到所需的设置,并提取其值。

我希望能够写出这样的东西:

// Desired usage:
Settings settings;      // A container class, defined below
settings.add(new Setting<string>("shipName", "HAL"));
settings.add(new Setting<int>   ("shipYear", 2001));

// Different return types:
string shipName = settings.getSetting("shipName")->getValue(); 
int shipYear    = settings.getSetting("shipYear")->getValue();

我有 3 节课:

  1. AbstractSetting,它是所有设置类的母亲。我需要这个,所以我可以有一个一致的类型来存储在一个向量中。

  2. Setting,一个继承自 AbstractSetting 的模板类。在这里,我可以将设置数据存储为字符串或整数。

  3. Settings,一个用于保存我的设置的容器类,它负责存储和检索。

方法主要是 getter/setter。由于实现很明显,为了简洁起见,我省略了它们。

我的问题是我应该在 AbstractSetting 中添加什么以允许我为 getValue() 提供不同的实现(具有不同的返回类型)?

class AbstractSetting
{
private:
   string mName;

public:
   AbstractSetting(const string &name);     // Constructor

   // What does here?  Need to declare getValue somehow
};

////////////////////////////////////////

// Sublcasses of AbstractSetting, one for each T
template <class T>
class Setting : public AbstractSetting
{
private:
   T mValue;

public:
   Setting<T>(const string &name, const T &value);

   void setValue(const T &value);
   T getValue();
};

////////////////////////////////////////

// Container for all our settings
class Settings 
{
private:
   Vector<AbstractSetting *> mSettings;

public: 
   const AbstractSetting *getSetting(const string &name) const;
   void add(AbstractSetting *setting);    // Store new setting
};
4

3 回答 3

2

我认为您必须告诉编译器您期望什么类型,您可以通过执行以下操作来做到这一点:

class AbstractSetting
{
  private:
   string mName;

  public:
   AbstractSetting(const string &name);     // Constructor

   template <typename T>
   T&  getTheValue()
   {
      Settings<T>* upcast = dynamic_cast<Settings<T>*>(this);
      if (!upcast)
         ; //throw your exception
      return upcast->getValue();
   }

   template <typename T>
   T const&  getTheValue() const
   {
      Settings<T>* upcast = dynamic_cast<Settings<T>*>(this);
      if (!upcast)
         ; //throw your exception
      return upcast->getValue();
   }
};

并调用它:

 string & value = settings.getSettings("strName").getTheValue<string>();
 int otherValue = settings.getSettings("intName").getTheValue<int>();

如果您不想指定返回类型,可以通过引用传递变量,方法如下:

class AbstractSetting
{
  private:
   string mName;

  public:
   AbstractSetting(const string &name);     // Constructor

   template <typename T>
   void  getTheValue(T& ret)
   {
      Settings<T>* upcast = dynamic_cast<Settings<T>*>(this);
      if (!upcast)
         ; //throw your exception
      ret = upcast->getValue();
   }
};

并像这样使用它:

 string value;
 int otherValue;
 settings.getSettings("stringName").getTheValue(value); // will do a copy, so this is kind of bad, avoid this by using pointer.
 settings.getSettings("intName").getTheValue(otherValue);
于 2013-08-01T13:22:29.103 回答
1

我将提供我找到的另一种解决方案,虽然我还不确定我是否喜欢它......这改编自: 为什么 C++ 不能从赋值中推断模板类型?

通过将其添加到 AbstractSettings 类:

template<class T>
operator T()
{
   return getValue<T>();
}

我可以覆盖 C++ 的隐式转换并使此语法起作用:

int shipYear = *settings.getSetting("shipYear");
于 2013-08-01T22:18:41.240 回答
0

这是另一个解决方案:

#include <iostream>
#include <map>
#include "some.hpp"

int main(int argc, char *argv[])
{
    using namespace std;

    map <string, some> settings;
    settings["shipName"] = string("HAL");
    settings["shipYear"] = 2001;

    string shipName = settings["shipName"];
    int    shipYear = settings["shipYear"];

    cout << shipName << " " << shipYear << endl;
}

小心,必须以与存储时完全相同的类型检索数据。这就是我使用的原因string("HAL");平原将"HAL"需要const char*.shipName

该解决方案的关键是 class ivl::some,它是库ivl的一部分,其工作方式与boost::any类似,但在数据可以适合预定大小时更有效地使用堆栈。

事实上,boost::any使用或多或少与您相同的方法,总是将数据放在堆上。它的源代码非常小,并且与您的解决方案更相关,因此它也可能有所帮助。

不是使用非模板基类和带有虚拟方法的模板派生类,而是使用some一个指向支持复制或删除操作的函数的指针,具体取决于第二个参数是否为零。

因为这个函数是一个静态模板方法的实例化,所以数据类型在函数体中是已知的,以及是否使用堆或堆栈内存。这会影响分配(new或放置new)和取消分配(delete或普通析构函数调用)。该决定完全基于数据的大小。

数据访问由简单地完成static_cast并要求用户指定类型,或者通过 method 显式指定_<T>(),或者通过转换运算符隐式指定到T&or ,其中自动推导出const T&模板参数。这些与前面的答案类似,但通过转换T也支持读写访问。T&

对于这个例子来说,一个精简的版本some.hpp就足够了,需要 100 行代码:

//-----------------------------------------------------------------------------

template <typename T>       void* away(T*       p) { return static_cast <void*>      (p); }
template <typename T> const void* away(const T* p) { return static_cast <const void*>(p); }
template <typename T>       void* ref (T&       r) { return away(&r); }
template <typename T> const void* ref (const T& r) { return away(&r); }

template <typename T>       T* back (void*       p) { return static_cast <T*>      (p); }
template <typename T> const T* back (const void* p) { return static_cast <const T*>(p); }
template <typename T>       T& deref(void*       p) { return *back <T>(p); }
template <typename T> const T& deref(const void* p) { return *back <T>(p); }

inline void*       peek(void*       p) { return deref <void*>      (p); }
inline const void* peek(const void* p) { return deref <const void*>(p); }

//-----------------------------------------------------------------------------

enum { stack_size = 8 };

template <int N = stack_size>
class some_
{
    union { char b[N]; void* p; };   // buffer; pointer
    void (*f)(void*&, const void*);  // operations

//-----------------------------------------------------------------------------

    template <typename T>
    static void stack(void*& dest, const void* src)
    {
        if (src) new (dest) T(deref <T>(src));
        else back <T>(ref(dest))->~T();
    };

    template <typename T>
    static void heap(void*& dest, const void* src)
    {
        if (src) dest = new T(deref <T>(peek(src)));
        else delete back <T>(dest);
    };

//-----------------------------------------------------------------------------

    template <typename T> bool fits() { return sizeof(T) <= N; }

    void read() { f = 0; }

    template <typename T>
    void read(const T& v)
    {
        fits <T>() ? new (b) T(v) : p = new T(v);
        f = fits <T>() ? stack <T> : heap <T>;
    }

    void read(const some_& s) { if ((f = s.f)) (*f)(p = b, s.b); }
    void free()               { if ( f )       (*f)(p, 0); }

    template <typename T>       void* ptr()       { return fits <T>() ? away(b) : p; }
    template <typename T> const void* ptr() const { return fits <T>() ? away(b) : p; }

protected:

//-----------------------------------------------------------------------------

                          some_& assign()           { free(); read();  return *this; }
    template <typename T> some_& assign(const T& v) { free(); read(v); return *this; }

public:

//-----------------------------------------------------------------------------

     some_() { read(); }
    ~some_() { free(); }

                          some_(const some_& s) { read(s); }
    template <typename T> some_(const T& v)     { read(v); }
    template <typename T> some_(const T  v[])   { read <const T*>(v); }

                          some_& operator=(const some_& s) { return assign(s); }
    template <typename T> some_& operator=(const T& v)     { return assign(v); }
    template <typename T> some_& operator=(const T  v[])   { return assign <const T*>(v); }

    some_& init()  { return assign(); }
    some_& clear() { return assign(); }

    bool empty()      const { return f == 0; }
    bool operator()() const { return f != 0; }

    template <typename T>       T* to()       { return back <T>(ptr <T>()); }
    template <typename T> const T* to() const { return back <T>(ptr <T>()); }

    template <typename T>       T& _()       { return *to <T>(); }
    template <typename T> const T& _() const { return *to <T>(); }

    template <typename T> operator       T&()       { return _<T>(); }
    template <typename T> operator const T&() const { return _<T>(); }
};

//-----------------------------------------------------------------------------

typedef some_<> some;

//-----------------------------------------------------------------------------

数据可以在一个some和另一个之间安全地复制,而无需指定类型。存储的大数据可以通过引用访问,也可以直接修改,但是需要类型。例如

string& shipName = settings["shipName"];

将启用容器shipName += ...内的修改。settings

将指针或 C 数组分配给 asome只会复制指针,而不是实际数据。C 数组存储为指针,因此必须在检索时指定相应的指针类型。

即使存储对象驻留在堆栈上,正确解构存储对象也是安全clear()的,并且可以知道它是否是。这些操作不需要类型。someempty()

存储数据的大小可以控制,但要统一。例如,如果您愿意为每个存储的项目分配 32 个字节,并仅在堆上为较大的项目分配空间,则可以改为定义

typedef some_<32> some;

默认值为 8 字节,与联合内的指针共享。这是 64 位上可能的最小值。对于大小为零的类型,some将需要专门的仅堆栈版本。some还包含一个指向函数的指针,因此它的大小通常为 16 字节。

嵌套容器some是可能的,例如构成异构数据的树。

完整版支持测试给定类型是否会成功,或者例如两个存储类型是否相等,使用一种特殊的type_id. 但无法自动恢复类型或进行转换。

更高级的结构 ,some_of在检索时根本不需要指定数据类型,但仅适用于数据的指定目标类型,例如ostream。这将像这样工作:

map <string, some_of <ostream> > settings;
settings["shipName"] = string("HAL");
settings["shipYear"] = 2001;

cout << settings["shipName"] << " " << settings["shipYear"] << endl;

为了实现这一点,some_of包含一个更多的函数指针。

使用自定义数据类型和自定义目的地,同样的想法可以应用于构建例如用于延迟函数调用的消息队列。

于 2013-08-02T03:09:21.063 回答