【问题标题】:Templates, Inheritance, and Virtual Methods (C++)模板、继承和虚拟方法 (C++)
【发布时间】:2013-08-01 13:02:22
【问题描述】:

我正在尝试编写一个通用设置管理器。设置来自 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
};

【问题讨论】:

  • 由于编译器在编译时无法知道类型,我认为您需要执行以下操作: (static_cast*>(settings.getSetting("strName")))- >getValue();
  • 在您的设置在抽象列表中之后,您必须以某种方式使类型信息已知。如果您丢失了这些信息,您可能会做出错误的演员表并出现运行时间问题。可能采取在Setting中添加函数返回AsString或AsInt等的方式。
  • 我已经用一个更好的用法示例更新了这个问题,以澄清我会提前知道在特定上下文中我期望什么类型。返回不同类型的不同方法是我最初的设计——它工作得很好,但我正在寻找更优雅的东西。
  • 好吧,编译器仍然需要知道在编译时要返回什么(例如,整数和浮点数在不同的寄存器上)。另外,多态性不考虑返回值。
  • 例如为什么不boost::variant

标签: c++ templates inheritance


【解决方案1】:

我认为你必须告诉编译器你期望什么类型,你可以这样做:

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);我不喜欢第二种,因为它破坏了标准的函数调用语义,我发现这使得理解代码更加困难。
【解决方案2】:

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

通过将其添加到 AbstractSettings 类:

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

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

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

【讨论】:

    【解决方案3】:

    这是另一个解决方案:

    #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 简单地完成,并要求用户通过方法_&lt;T&gt;() 显式指定类型,或通过转换运算符隐式指定类型T&amp;const T&amp;,其中模板参数T是自动推导出来的。这些与之前的答案类似,但通过T&amp; 转换也支持读写访问。

    对于这个例子来说足够的 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 和另一个some 之间安全地复制,而无需指定类型。存储的大数据可以通过引用访问,也可以直接修改,但是需要类型。例如

    string& shipName = settings["shipName"];
    

    将在 settings 容器中启用像 shipName += ... 这样的修改。

    将指针或 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 包含一个函数指针。

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

    【讨论】:

      猜你喜欢
      • 2015-09-09
      • 1970-01-01
      • 1970-01-01
      • 2014-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多