【问题标题】:How do I store and access a type dynamically in c++?如何在 C++ 中动态存储和访问类型?
【发布时间】:2015-07-21 12:01:06
【问题描述】:

我知道 c++ 模板,它允许您为多种类型编写代码,但是如果我想动态存储和访问一个类型怎么办?为什么这在 C++ 中这么难做?

我非常希望必须这样做:

enum SupportedTypes
{
    IntType,
    FloatType,
    StringType
}

template <typename T>
class ClassThing
{
    public:
        T Value;
        SupportedTypes Type;
}

...

//Not sure if you could even access thing->Type, but regardless, you get the idea...
switch (thing->Type)
{
    case IntType:
        DoSomething(((ClassThing<int>*)thing)->T);
        break;
    case FloatType:
        DoSomething(((ClassThing<float>*)thing)->T);
        break;
    case StringType:
        DoSomething(((ClassThing<string>*)thing)->T);
        break;
}

为什么 c++ 不支持这样的东西:

int whatIsThis = 5;
type t = typeid(whatIsThis); //typeid exists, but you can't do...:
t anotherInt = 5;

?

另一个我比较乐观的问题是:如果您选择采用模板化路线,如果您将其一般存储在集合中,是否有任何方法可以维护该类型?例如:

vector<ClassThing> things;

(顺便说一句,这将给出“类模板的参数列表...丢失”错误。)我的猜测是,不,这是不可能的,因为上述是不可能的。

【问题讨论】:

    标签: c++ templates dynamic types store


    【解决方案1】:

    如何在 c++ 中动态存储和访问类型?

    有很多选项可供选择:

    • 使用运行时多态,你有一个基类,它可能为每个支持的类型提供一些通用功能和派生类;您经常需要对接口应该有多“胖”(提供仅对派生类型的子集有意义的基类函数)与强制客户端使用dynamic_cast&lt;&gt; 来恢复/打开运行时做出一些选择输入

      • 一种特别强大的技术是让派生类成为同一模板的特定类型实例化,因为这意味着您可以参数化地支持任意类型,即,如果它们提供模板期望的使用语义
    • 使用可区分的联合(基本上,类型标识 enum/int 以及受支持类型的联合) - std::variant&lt;&gt; 是一个不错的选择

    • 在创建/存储值捕获时,您必须知道它的类型

      • 您可以记录其typeinfo 和地址,然后在以后访问变量时可以使用typeinfo 来测试对象是否属于特定类型 - 尝试每种支持的类型直到找到匹配项 - @987654324 @ 是一个不错的选择,

      • 您可以使用函数指针或std::function&lt;&gt; 捕获任意一组特定于类型的操作

    为什么 c++ 不支持这样的东西:

    int whatIsThis = 5;
    type t = typeid(whatIsThis); //typeid exists, but you can't do...:
    t anotherInt = 5;?
    

    确实如此,decltypeauto

    int whatIsThis = 5;
    using t = decltype(whatIsThis);
    t anotherInt = 5;
    
    auto anotherWhatever = whatIsThis; // another way to create an additional
                                       // variable of the same type
    

    对于运行时多态性,您可能实际上想了解工厂(创建多种类型的对象之一 - 所有都派生自一些基本接口 - 给定一些运行时输入)和克隆函数(创建变量的副本运行时类型未知)。

    如果您选择采用模板化路线,如果您将其一般存储在集合中,是否有任何方法可以维护该类型:vector&lt;ClassThing&gt; things;(顺便说一下,这将给出“argument list for class template ... is missing”错误。)

    如果不实例化模板,您甚至无法从模板创建单个对象,因此也不可能拥有完整的vector。一种合理的方法是从基类派生模板并将[智能] 指针或std::reference_wrappers 存储到vector 中的基类。

    【讨论】:

    • decltype 是我最好的新朋友。 :)
    【解决方案2】:
    int x = 5;
    decltype(x) y = 4;
    auto z = 3;
    

    decltype(a) 将为您提供a 的类型。然后,您可以使用typedef 来存储类型,或者在必要时使用其他函数从类型中删除引用。

    例如:

    typedef decltype(a) type1; 
    type1 b = 2 * a;
    

    auto 让您根本不需要指定类型。

    您唯一需要的是在 c++11 模式 (-std=c++11) 或更高版本中编译。

    至于向量问题,decltype 也可以。

    【讨论】:

      【解决方案3】:

      我不会窃取答案,但我会为那些试图做类似事情的人提供我最终使用的方法。 (我正在用 memcpy 编写自己的原始序列化和反序列化代码。)我希望做的是存储和维护各种类型的排列,而不必创建一堆结构或类,例如(来自我的问题):

      template <typename T>
      class ClassThing
      {
          public:
              T Value;
              SupportedTypes Type;
      }
      
      //Then store everything in a:
      vector<ClassThing> things;
      

      但是,尝试将模板化类存储在向量中会出现“类模板的参数列表...丢失”错误,因为正如 Tony D 在他的回答中所说,“您甚至无法创建单个对象从模板而不实例化它......”我也不想使用任何外部库,如 boost(用于变体)。

      因此,我得出结论,因为我绝对想使用单个集合来存储所有结构,所以我根本无法使用模板类。相反,我决定使用 模板化构造函数(仅)和 void* 作为值,并存储 类型的哈希存储/复制类型所需的字节数

      class ClassThing
      {
          public:
              void* Value;
      
              unsigned long long TypeHash;
      
              unsigned long long NumberOfBytes;
      
              template <typename T>
              ClassThing(T passedValue)
              {
                  Value = &passedValue;
                  TypeHash = typeid(passedValue).hash_code();
                  NumberOfBytes = sizeof(T);
              }
      
              //For strings, do this:
              ClassThing(const char* passedValue, unsigned short passedNumberOfBytes)
              {
                  Value = const_cast<char*>(passedValue);
                  TypeHash = typeid(char*).hash_code();
                  NumberOfBytes = length;
              }
      }
      

      不幸的是,这个解决方案丢失了类型,但是由于我使用的序列化和反序列化过程是一个简单的 memcpy,我只需要一个指向数据的指针和它使用的字节数.我在此处存储类型的哈希值的原因是,我可以在序列化之前执行类型检查(例如,确保浮点数没有被序列化到 int 应该在的位置)。

      对于反序列化过程,我将使用这种技术:https://stackoverflow.com/a/15313677/1599699

      由于我不知道类型,我只需要期望来自 void* 的强制转换与序列化过程相匹配,尽管我至少可以检查 NumberOfBytes 值,理想情况下也可以检查 TypeHash,如果这些可用的话.在反序列化结束时,我会得到一个 void* 并这样做:

      void* deserializedData = ...;
      float deserializedFloat = *(float*)&deserializedData;
      

      这当然不是我问题的理想解决方案,但它允许我做我想做的事,即极高性能的序列化和反序列化为二进制文件,内存使用量极低,维护成本极低。

      希望这对某人有所帮助!

      【讨论】:

      • 关于“……无需创建一堆结构或类”。为什么?我认为许多初学者/中级 C++ 程序员出于某种原因认为定义结构是一件大事。我经常使用一次性结构作为一个函数的返回类型或参数类型。需要一个你没有的结构?定义它!
      • @Ben 我不是,也不是初学者/中级。你倒过来了。大量的结构和类使您的代码变得僵硬并且难以在以后更改,尤其是当有很多代码与它们相关时。
      • 这是一个有趣的观点。我的经验是,只有很少的单体类会使代码难以更改。
      【解决方案4】:

      虽然这不完全是 C++ 答案(而是 C 答案),但它在 C++ 中应该同样有效。

      void* 类型是指向无类型内存的指针。基本上,您可以将其转换为任何类型的指针,然后取消引用。示例:

      int x1 = 42;
      long l1 = 123456789L;
      
      void* test = &x1;
      int x2 = *(int*)test; // x2 now contains the contents of x1
      
      test = &l1;
      long l2 = *(long*)test; // l2 now contains the contents of l1
      

      这绝不是解决问题的最巧妙方法,但它是一种选择。

      进一步阅读:
      https://www.astro.umd.edu/~dcr/Courses/ASTR615/intro_C/node15.html
      http://www.circuitstoday.com/void-pointers-in-c
      http://www.nongnu.org/c-prog-book/online/x658.html

      【讨论】:

      • 我知道 void*,但这需要我手动实现自己的类型转换(即 switch 语句,就像我的问题一样),不是吗?问题不在于如何存储类型,而在于如何在之后检索它们。
      【解决方案5】:

      如果您想要动态类型(C++11 或更好的类型,例如 C++14),您可以通过使用一些联合创建 class 来创建 variant 类型:

       class Thing {
         enum SupportedTypes type;
         union {
           intptr_t num; // when type == IntType
           double flo; // when type == FloatType
           std::string str; // when type == StringType
         }
         // etc....
       };
      

      小心,您需要遵守rule of five,并且您可能应该显式str 上调用std::string 的析构函数,而type == StringType 等...

      一些第三方库可能会有所帮助:Boost variants、Qt QVariant 等...

      【讨论】:

      • 鉴于对 OP 显示的 C++ 功能/问题缺乏认识,建议 boost::variant&lt;&gt; 比尝试重新创建相同类型的功能更实际。
      • 但是 Boost 不是标准的
      • 也许我不想使用 Boost。像这样的 Vanilla c++ 变体不能解决我的问题,因为它们需要使用开关等(没有动态运行时类型检索),所以你在这里真正提供的唯一答案是使用这些库。
      猜你喜欢
      • 1970-01-01
      • 2011-09-11
      • 1970-01-01
      • 2020-03-06
      • 1970-01-01
      • 2023-03-08
      • 2022-01-01
      • 2016-11-14
      • 2015-12-27
      相关资源
      最近更新 更多