2012-01-05 113 views
1

在我當前的項目中,我有很多不同格式的二進制文件。其中有幾個作爲簡單的檔案,因此我試圖想出一個很好的方法將提取的文件數據傳遞給其他類。分割文件並將數據傳遞給其他類

這是我目前的做法的一個簡單的例子:

class Archive { 
    private: 
     std::istream &fs; 
     void Read(); 
    public: 
     Archive(std::istream &fs); // Calls Read() automatically 
     ~Archive(); 
     const char* Get(int archiveIndex); 
     size_t GetSize(int archiveIndex); 
}; 

class FileFormat { 
    private: 
     std::istream &fs; 
     void Read(); 
    public: 
     FileFormat(std::istream &fs); // Calls Read() automatically 
     ~FileFormat(); 
}; 

存檔類基本分析存檔和讀取存儲的文件到char指針。 爲了從Archive加載第FileFormat文件,我將目前使用下面的代碼:(請注意,在存檔一些文件可能有其他的檔案,但不同的格式)

std::ifstream fs("somearchive.arc", std::ios::binary); 
Archive arc(fs); 
std::istringstream ss(std::string(arc.Get(0), arc.GetSize(0)), std::ios::binary); 
FileFormat ff(ss); 

讀取二進制數據時,我使用了一個BinaryReader類的功能,如這些:

BinaryReader::BinaryReader(std::istream &fs) : fs(fs) { 
} 

char* BinaryReader::ReadBytes(unsigned int n) { 
    char* buffer = new char[n]; 
    fs.read(buffer, n); 
    return buffer; 
} 

unsigned int BinaryReader::ReadUInt32() { 
    unsigned int buffer; 
    fs.read((char*)&buffer, sizeof(unsigned int)); 
    return buffer; 
} 

我喜歡這種方法的簡單,但我目前有很多內存兒的掙扎rors和SIGSEGVs,我擔心這是因爲這種方法。一個例子是當我在一個循環中重複創建和讀取一個檔案時。它適用於大量迭代,但過了一段時間,它開始讀取垃圾數據。

我對你的問題是,如果這種方法是可行的(在這種情況下,我問我做錯了什麼),如果不是,有什麼更好的方法嗎?

+0

你沒有顯示存檔類的實現,我認爲用std :: ios :: binary打開istream? – Benj 2012-01-05 17:32:55

+0

我在這裏寫的代碼中忘記了std :: ios :: binary,但它在我的版本中。 istream是從ifstream構建的,並且該流用std :: ios :: binary打開,如上所示。 – Merigrim 2012-01-05 18:22:32

回答

2

在OP中的代碼缺陷是:

  1. 您分配堆內存,並從你的函數中返回一個指針。這可能會導致內存泄漏。你沒有泄漏問題(現在),但是在設計你的課程時你必須記住這些東西。
  2. 處理Archive和FileFormat類時,用戶始終必須考慮到存檔的內部結構。基本上它會影響數據封裝的想法。

當你的類框架的用戶創建一個存檔對象時,他只是得到一個方法來提取一些原始數據的指針。然後用戶必須將這些原始數據傳遞給完全獨立的類。你也將有不止一種FileFormat。即使不需要注意處理這種系統的泄漏堆分配,也會非常容易出錯。

讓我們嘗試將一些面向對象的原則應用於任務。您的存檔對象是不同格式文件的容器。因此,存檔的等價得到()一般應返回文件對象,而不是一個指向原始數據:

//We gonna need a way to store file type in your archive index 
enum TFileType { BYTE_FILE, UINT32_FILE, /*...*/ } 

class BaseFile { 
public: 
virtual TFileType GetFileType() const = 0; 
/* Your abstract interface here */ 
}; 

class ByteFile : public BaseFile { 
public: 
ByteFile(istream &fs); 
virtual ~ByteFile(); 
virtual TFileType GetFileType() const 
{ return BYTE_FILE; } 
unsigned char GetByte(size_t index); 
protected: 
/* implementation of data storage and reading procedures */ 
}; 

class UInt32File : public BaseFile { 
public: 
UInt32File(istream &fs); 
virtual ~UInt32File(); 
virtual TFileType GetFileType() const 
{ return UINT32_FILE; } 
uint32_t GetUInt32(size_t index); 
protected: 
/* implementation of data storage and reading procedures */ 
}; 


class Archive { 
public: 
Archive(const char* filename); 
~Archive(); 
BaseFile* Get(int archiveIndex); 
{ return (m_Files.at(archiveIndex)); } 
/* ... */ 
protected: 
vector<BaseFile*> m_Files; 
} 

Archive::Archive(const char* filename) 
{ 
    ifstream fs(filename); 

    //Here we need to: 
    //1. Read archive index 
    //2. For each file in index do something like: 
    switch(CurrentFileType) { 
    case BYTE_FILE: 
      m_Files.push_back(new ByteFile(fs)); 
      break; 
    case UINT32_FILE: 
      m_Files.push_back(new UInt32File(fs)); 
      break; 
    //..... 
    } 
} 

Archive::~Archive() 
{ 
    for(size_t i = 0; i < m_Files.size(); ++i) 
     delete m_Files[i]; 
} 

int main(int argc, char** argv) 
{ 
    Archive arch("somearchive.arc"); 
    BaseFile* pbf; 
    ByteFile* pByteFile; 

    pbf = arch.Get(0); 

    //Here we can use GetFileType() or typeid to make a proper cast 
    //An example of former: 

    switch (pbf.GetFileType()) { 
    case BYTE_FILE: 
     pByteFile = dynamic_cast<ByteFile*>(pbf); 
     ASSERT(pByteFile != 0); 
     //Working with byte data 
     break; 
    /*...*/ 
    } 

    //alternatively you may omit GetFileType() and rely solely on C++ 
    //typeid-related stuff 

} 

那只是可以簡化您的應用程序檔案的使用類的一個總體思路。

請記住,良好的類設計可以幫助您防止內存泄漏,代碼澄清等。但是,無論你有什麼類,你仍然會處理二進制數據存儲問題。例如,如果您的存檔存儲了64字節的字節數據和8個uint32,並且您以某種方式讀取了65個字節而不是64個字節,則讀取以下整數將會給您帶來垃圾。您可能還會遇到對齊和排序問題(如果您的應用程序應該在多個平臺上運行,則後者非常重要)。儘管如此,良好的課堂設計可能會幫助您制定更好的代碼來解決這些問題。

+0

我真的很喜歡這種方法!一旦我嘗試了,我會再次寫在這裏,如果它的工作,我會接受你的答案。 – Merigrim 2012-01-05 18:28:54

+0

雖然實施這個我有一個問題,但。如果我不想在需要時將數據讀入內存,該怎麼辦?例如,如果我有兩個文件的存檔,現在需要一個,但另一個只在極少數情況下需要。這種系統很容易做到嗎?這不是一個可怕的記憶開銷,但它可能在以後很重要。 – Merigrim 2012-01-05 19:01:42

+0

另一個問題:將ifstream傳遞給文件類會使文件中的絕對搜索變得困難。我是否必須用它們各自的偏移量初始化它們,以便它們可以執行fs.seekg(offset + x)還是有更好的方法? – Merigrim 2012-01-05 21:46:48

2

這是要求麻煩從你的函數傳遞一個指針,並希望用戶知道要刪除它,除非函數名稱很明顯這樣做,例如,一個以單詞create開頭的函數。

所以

Foo * createFoo(); 

很可能是創建一個對象,用戶必須刪除的功能。

對於初學者來說,一個優選的解決方案是返回std::vector<char>或允許用戶將std::vector<char> &傳遞給函數,並且將字節寫入其中,並在必要時設置其大小。 (如果在可以重複使用相同緩衝區的地方進行多次讀取,這會更有效)。

你還應該學習const正確性。

至於你的「一段時間後它填滿垃圾」,你在哪裏檢查文件的結尾?

+0

我正在研究你現在寫的東西。至於垃圾數據,在讀取檔案時(讀取文件入口並使用seekg(偏移量),讀取指定長度的數據,然後看到kg(orig_pos)),EOF永遠不會到達。當我創建一個歸檔對象(因此將文件讀入內存)並在之後立即刪除它時,問題就出現了。請注意,在此過程中沒有(明顯的)內存泄漏。 – Merigrim 2012-01-05 15:56:25

+0

這並不是壞建議,但它不太可能幫助OP解決他們原來的問題。同時返回一個向量只是在C++ 11中的好建議,其中移動構造將使其成爲高效操作。在向量中傳遞的另一種選擇將是任何C++實現中的一個很好的選擇。 – Benj 2012-01-05 17:26:47

+0

很難看到OP的實際問題,我猜想任何內存問題可能都是由於必須進行太多的內​​存管理而產生的,並且在沒有任何先前知識的情況下,垃圾結果可能來自分配緩衝區,但未能讀取字節(如果已經達到EOF)。 – CashCow 2012-01-05 18:43:42

相關問題