如何在C/ c++中通读结构体的类型

How to read through types of a struct in C/C++

本文关键字:结构体 类型 c++      更新时间:2023-10-16

我试图在不同的结构中找到任何给定变量的"类型",并能够读取它们。(记住这是伪代码)

例如:

#include "stream.h" //a custom stream reader class I made
typedef unsigned char BYTE;
/***** SERIES OF DIFFERENT STRUCTS ******/
struct asset
{
    char *name;
    int size;
    BYTE *data;
};
struct asset2
{
    char *lang;
    char *entry;
};
/*****************************************/

    void readAsset( Enumerable<struct> &istruct)
    {
        foreach( object o in istruct )
        {
            switch( o )
            {
                case int:
                    &o = _stream->ReadInt32();
                    break;
                case char:
                    &o = _stream->ReadChar();
                    break;
                case *:
                    &o = _stream->ReadInt32();
                    break;
                default: break;
            }
        }
    }

我希望它能够做以下事情:

asset a1;
asset2 a2;
readAsset( a1 );
readAsset( a2 );

并将文件中的所有信息传递给a1和a2。

我想知道C/c++中是否有一种方法可以从结构中的任何对象获得数据的类型,然后据此读取?复杂枚举是否可行?对不起,我的代码很糟糕,但我想让它更容易理解我正在尝试做什么。

额外的信息:

_stream是一个指向Stream类的指针,它类似于。net中的Stream Reader,它从文件中读取数据,并根据读取的数据量推进其位置。

如果你不明白我在问什么,我很乐意重新表述。

如果不列出结构体的所有成员,就无法遍历它们。

您可以在编译时使用c++ 11中的::std::tuple迭代类似结构的东西。

你也不能以那种方式切换类型。您可以这样做,但是您这样做的方法是使用几个具有相同名称的函数,每个函数使用不同的参数类型。比如:

 void doRead(StreamType &stream, int &data)
 {
     data = stream.readInt32();
 }
 void doRead(StreamType &stream, char &data)
 {
     data = stream.readChar();
 }
 // etc...

然后你只需用你的结构成员调用doRead,然后噗的一声编译器就会神奇地根据类型选择正确的。

在c++中,解决这里要解决的问题的方法是一个序列化库。如果您可以控制写入的格式和读取的格式,则可以使用类似protobuf或boost::serialization的东西来相对容易地完成此操作,而无需编写大量自己的代码。

另外,您的代码有几个问题。不要在标识符中使用_开头字符。以_为前导的标识符保留供编译器或标准库实现使用。许多编译器都有特殊的关键字,这些关键字是编译器特定的语言扩展,以_字符开头。在某些环境中,使用以_为前导字符的标识符可能会导致代码神秘地无法编译,并出现各种奇怪的无法理解的错误。


你可以得到一个在编译时可枚举的结构体。但它很丑:

#include <tuple>
#include <string>
#include <vector>
#include <type_traits>
class asset : public ::std::tuple< ::std::string, ::std::vector<BYTE> >
{
 public:
   ::std::string &name()                   { return ::std::get<0>(*this); }
   const ::std::string &name() const       { return ::std::get<0>(*this); }
   ::std::vector<BYTE> &data()             { return ::std::get<1>(*this); }
   const ::std::vector<BYTE> &data() const { return ::std::get<1>(*this); }
};
void writeToStream(Stream *out, const ::std::string &field)
{
   out->writeString(field);
}
void writeToStream(Stream *out, const ::std::vector<BYTE> &field)
{
   out->writeInt(field.size());
   out->writeRaw(field.data(), field.size());
}
template <unsigned int fnum, typename... T>
typename ::std::enable_if< (fnum < sizeof...(T)), void >::type
writeToStream_n(Stream *out, const::std::tuple<T...> &field)
{
   writeToStream(out, ::std::get<fnum>(field));
   writeToStream_n<fnum+1, T...>(out, field);
}
template <unsigned int fnum, typename... T>
typename ::std::enable_if< (fnum >= sizeof...(T)) >::type
writeToStream_n(Stream *, const::std::tuple<T...> &)
{
}
template <typename... Tp>
void writeToStream(Stream *out, const ::std::tuple<Tp...> &composite)
{
   writeToStream_n<0, Tp...>(out, composite);
}
void foo(Stream *out, const asset &a)
{
   writeToStream(out, a);
}

注意,对于asset类型没有显式的writeToStream。编译器将在运行时通过解压缩它派生的::std::tuple并写出每个单独的字段来编写它。

同样,如果你有裸指针,你写的c++很差。如果你要写c++的话,请写出习惯的、好的c++。你想用运行时反射做的这一切都不是正确的方法。

这就是我将您的char *name转换为::std::string和您的sizedata字段表示的大小分隔的BYTE数组转换为::std::vector的原因。使用这些类型是编写c++的惯用正确方法。像你以前那样使用指针可不是。此外,如果有两个字段(datasize)具有强相关的值,但没有任何行为或任何其他迹象表明它们是相关的,那么即使是在运行时进行自省的编译器也很难找出正确的事情要做。它无法知道data指向的BYTE数组有多大,也无法知道您决定在size中对其进行编码。

你要求的是所谓的反射-这是:

计算机程序检查的能力并修改结构和行为(特别是值),对象运行时的元数据(属性和函数)。

c++并没有"本地

我的意思是-已经有一些尝试引入它的某些方面-取得了不同程度的成功-产生了反射的某些方面,但不是"完整的"反射本身,因为你将在Ruby之类的语言中获得。

然而,如果你想冒险,你可以尝试一个名为Reflectabit的反射库:

看看它是否值得(它可能考虑到你的代码),这里引用了它-其中有相当多的如何使用API的例子:

http://www.altdevblogaday.com/2011/09/25/reflection-in-c-part-1-introduction/

祝你好运!

c++中通常的模式不是试图找出该类型的成员是什么,而是提供一个操作符,由该类型的实现者实现,能够序列化/反序列化到磁盘。

例如,您可以看一下boost::serialize库。用法不是太复杂,你只需要提供一个函数,按某种顺序列出你的成员,然后库将从那里获得它,并实现不同格式的序列化。