2010-03-04 55 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; 
}