模板,继承和虚方法(C ++)

时间:2013-08-01 13:02:22

标签: c++ templates inheritance

我正在尝试编写通用设置管理器。设置来自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. 设置,一个继承自AbstractSetting的模板化类。在这里,我可以将设置数据存储为字符串或整数。

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

  4. 方法主要是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
    };
    

3 个答案:

答案 0 :(得分: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);

答案 1 :(得分:1)

我会提供另外一个我找到的解决方案,不过我还不确定我是否喜欢它...这是改编自: Why can't C++ deduce template type from assignment?

将其添加到AbstractSettings类:

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

我可以覆盖C ++的隐式转换并使这种语法有效:

int shipYear = *settings.getSetting("shipYear");

答案 2 :(得分: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

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

实际上,boost::any使用的方法与您的方法大致相同,始终将数据放在堆上。它的源代码非常小,与您的解决方案更相关,所以它也可能有所帮助。

some使用单个指向函数来支持复制或删除操作,而不是使用非模板基类以及带有虚方法的模板派生类,而不是第二个参数是否为零。

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

数据访问只需static_cast完成,并要求用户通过方法_<T>()明确指定类型,或通过转换运算符隐式指定T&或{{1} },其中自动推导出模板参数const T&。这些与前面的答案类似,但通过T转换也支持读写访问。

这个示例的T&的精简版本需要100行代码:

some.hpp

可以在一个//----------------------------------------------------------------------------- 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之间安全地复制数据,而无需指定类型。可以通过引用访问大型存储数据或直接修改,但是需要类型。 E.g。

string& shipName = settings["shipName"];

会在shipName += ...容器中启用settings之类的修改。

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

clear() some是安全的,即使它存在于堆栈中,也可以正确解析存储的对象,并且可以知道它是否为empty()。这些操作不需要任何类型。

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

typedef some_<32> some;

默认值为8个字节,与联合中的指针共享。这是64位的最小可能性。对于零大小的类型,将需要专用的仅堆栈版本的somesome还包含一个指向函数的指针,因此它的大小通常为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还包含一个指向函数的指针。

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