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

How do I store and access a type dynamically in c++?

我知道 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;

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

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 也适用。

尽管这不完全是 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

如果您想要动态类型(在 C++11 or better, e.g. C++14) you could make a variant 中,通过使用一些联合创建 class 类型:

 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,等等...

How do I store and access a type dynamically in c++?

有多种选择可供选择:

  • 使用运行时多态性,其中您有一个基础 class 可以为每个支持的类型提供一些通用功能和派生的 class;你经常不得不对你的界面应该有多“胖”做出一些选择(提供仅对派生类型的子集有意义的基本 class 函数)与强制客户端使用 dynamic_cast<> 到 recover/switch-on 运行时类型

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

  • 当 creating/storing 捕获值时,您一定会知道它的类型

    • 你可以同时记录它typeinfo and address, then when accessing the variable later you can use the typeinfo to test whether the object is of a specific type - trying each supported type until a match is found - std::any<>这个是不错的选择,或者

    • 您可以使用函数指针或std::function<>

      捕获任意一组特定于类型的操作

Why doesn't c++ support something like this:

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

确实如此,decltype and auto:

int whatIsThis = 5;
using t = decltype(whatIsThis);
t anotherInt = 5;

auto anotherWhatever = whatIsThis; // another way to create an additional
                                   // variable of the same type

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

if you choose to go the templated route, is there any way to maintain the type if you store it generically in a collection: vector<ClassThing> things; (This will give an "argument list for class template ... is missing" error, by the way.)

如果不实例化模板,您甚至无法从模板创建单个对象,因此也不可能拥有整个 vector。一个合理的方法是从基数 class 派生模板,并在 vector.

中存储指向基数 class 的 [smart] 指针或 std::reference_wrappers

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

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

//Then store everything in a:
vector<ClassThing> things;

但是,尝试将模板化的 class 存储在向量中会出现 "argument list for class template ... is missing" 错误,因为正如 Tony D 在他的回答中所说,"You can't create even a single object from a template without instantiating it..." 我也不想使用任何外部库,如 boost(用于变体)。

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

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,我所需要的只是一个指向数据的指针和它使用的字节数。我将类型的散列存储在这里的原因是,我可以在序列化之前执行类型检查(例如,确保 float 没有在应该序列化 int 的地方进行序列化)。

对于反序列化过程,我将使用这种技术:

因为我不知道类型,所以我只需要期待 void* 的转换与序列化过程相匹配,尽管我至少可以检查 NumberOfBytes 值,理想情况下还可以检查 TypeHash,如果那些可用。在反序列化结束时,我将以 void* 结束并执行此操作:

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

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

希望这对某人有所帮助!