2010-03-04 64 views
0

假设在一个程序的内存布局,我给出:C++搞清楚成员编程

class Foo { 
    int x; 
    double y; 
    char z; 
}; 

class Bar { 
    Foo f1; 
    int t; 
    Foo f2; 
}; 

int main() { 
    Bar b; 
    bar.f1.z = 'h'; 
    bar.f2.z = 'w'; 
    ... some crap setting value of b; 
    FILE *f = fopen("dump", "wb"); // c-style file 
    fwrite(&b, sizeof(Bar), 1, f); 
} 

假设在其他程序中,我有:

int main() { 
    File *f = fopen("dump", "rb"); 
    std::string Foo = "int x; double y; char z;"; 
    std::string Bar = "Foo f1; int t; Foo f2;"; 

    // now, given this is it possible to read out 
    // the value of bar.f1.z and bar.f2.z set earlier? 
} 

什么我问的是: 鉴于我有一个类的类型,我可以弄清楚C++如何显示它?

+2

1)您正在打开的“文本”模式的文件,使用二进制输出功能'fwrite'。要小心,这在一些平台上可行,但在其他平台上不行。 2)一个类的成员默认是私有的,你必须使用'struct',或者声明它们是公共的。 – AraK 2010-03-04 23:58:39

+0

@Arak +1指出错误;这是否更好? – anon 2010-03-04 23:59:34

回答

4

您需要研究“序列化”。有一个图书馆,提升序列号,人们一直在推荐。

FWIW,我建议不要在类,结构和联合上使用fwritestd::ostream::write。允许编译器在成员之间插入填充,因此可能会写入垃圾。另外,指针不会很好序列化。

要回答你的问题,为了确定从哪个结构中加载数据,你需要某种标识来指示对象类型。这可以是从enum到对象名称的任何内容。

也调查Factory设计模式。

1

你不能假设任何关于表示Bar的字节顺序。如果文件跨越系统或者该程序使用不同的标志编译,那么您将以不同的顺序读取和写入。

我已经看到了解决方法,但它可能只适用于非常简单的类型。

,我从raknet教程引用:

#pragma pack(push, 1) 
struct structName 
{ 
    unsigned char typeId; // Your type here 
    // Your data here 
}; 
#pragma pack(pop) 

注意的使用#pragma包(推,1)和#pragma包(POP)?这些强制你的编译器(在这种情况下是VC++)将结构打包为字节对齐。检查你的编译器文档以了解更多。

你想要序列化。

+0

这是我用过的解决方案。如果你意识到这种方法的局限性,这是 - IMO - 最简单的方法。 – 2010-03-05 00:38:29

3

我不太清楚你问什么,所以我会采取一个飞跃......

如果你真的需要找出其中的字段是一个结构,使用offsetof

请注意链接页面中的“POD”限制。这是一个C宏,出于兼容性原因包含在C++中。我们现在应该使用成员指针,尽管成员指针并不能解决所有相同的问题。

“offsetof”基本上想象你的结构在地址为零的一个实例,然后查看你感兴趣的字段的地址。如果你的结构/类使用多重或虚拟继承,这会变得非常错误,因为找到该领域然后涉及(通常)虚拟表中的检查。由于地址为零的虚构实例不存在,因此它没有虚拟表指针,因此您可能会遇到某种访问冲突崩溃。

一些编译器可以解决这个问题,因为他们已经用一个知道结构布局的内在替代了传统的offsetof宏,而没有尝试去做虚构实例的诡计。即便如此,最好不要依赖于此。

但是,对于POD结构,offsetof是找到特定字段的偏移量的便捷方式,而安全的方式是,它可以确定实际偏移量,而不考虑平台应用的对齐方式。

对于一个的sizeof场,你明明只是使用sizeof。这只是留下特定于平台的问题 - 上,由于定位,endianness等;-)

编辑

可能是一个愚蠢的问题,不同的平台等不同的布局,但为什么不从文件FREAD数据直接在结构的实例中,基本上做了你用fwrite做的事情,但是反过来呢?

你会得到相同的可移植性问题同上,这意味着你的代码,如果使用不同的选择,不同的编译器或不同的平台重新编译可能无法读取自己的文件。但对于单一平台的应用程序来说,这种方式效果很好。

0

对于你给的例子,它看起来像你真的需要某种Ç解析器会与您的类型声明解析字符串。然后你就可以用正确的方式解释你从文件中读取的字节。

的Structs用C构件被布局为构件声明的顺序。编译器可以根据平台特定的对齐需要在成员之间插入填充。变量的大小也是平台特定的。

0

如果你有过,你可以使用成员指针类控制。你绝对可以可以这样做。现在的问题是,是否你应该 ...

class Metadata 
{ 
public: 
    virtual int getOffset() = 0; 
}; 

template <typename THost, typename TField> 
class TypedMetadata : Metadata 
{ 
private: 
    TField (THost::*memberPointer_); 

    TypedMetadata(TField (THost::*memberPointer)) 
    { 
     memberPointer_ = memberPointer; 
    } 

public: 
    static Metadata* getInstance(TField (THost::*memberPointer)) 
    { 
     return new TypedMetadata<THost, TField>(memberPointer); 
    } 

    virtual int getOffset() 
    { 
     THost* host = 0; 

     int result = (int)&(host->*memberPointer_); 

     return result; 
    } 
}; 

template<typename THost, typename TField> 
Metadata* getTypeMetadata(TField (THost::*memberPointer)) 
{ 
    return TypedMetadata<THost, TField>::getInstance(memberPointer); 
} 

class Contained 
{ 
    char foo[47]; 
}; 

class Container 
{ 
private: 
    int x; 
    int y; 
    Contained contained; 
    char c1; 
    char* z; 
    char c2; 

public: 
    static Metadata** getMetadata() 
    { 
     Metadata** metadata = new Metadata*[6]; 

     metadata[0] = getTypeMetadata(&Container::x); 
     metadata[1] = getTypeMetadata(&Container::y); 
     metadata[2] = getTypeMetadata(&Container::contained); 
     metadata[3] = getTypeMetadata(&Container::c1); 
     metadata[4] = getTypeMetadata(&Container::z); 
     metadata[5] = getTypeMetadata(&Container::c2); 

     return metadata; 
    } 
}; 

int main(array<System::String ^> ^args) 
{ 
    Metadata** metadata = Container::getMetadata(); 

    std::cout << metadata[0]->getOffset() << std::endl; 
    std::cout << metadata[1]->getOffset() << std::endl; 
    std::cout << metadata[2]->getOffset() << std::endl; 
    std::cout << metadata[3]->getOffset() << std::endl; 
    std::cout << metadata[4]->getOffset() << std::endl; 
    std::cout << metadata[5]->getOffset() << std::endl; 

    return 0; 
}