2012-01-05 3 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ポインタに格納されたファイルを読み込みます。

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); 

(アーカイブ内のいくつかのファイルには、追加のアーカイブが、異なる形式のものであってもよいことに注意してください。)

: が Archiveから最初 FileFormatファイルをロードするためには、私は現在、次のコードを使用します

バイナリデータを読むとき、私はこれらのような機能を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; 
} 

私は、このアプローチの単純さを好むが、私は現在メモリERの多くに苦しみましたrorsとSIGSEGVsと私はそれがこの方法のためだと思う。一例は、ループ内でアーカイブを繰り返し作成して読み込む場合です。これは多数の反復で動作しますが、しばらくしてから、代わりに迷惑データの読み込みを開始します。このアプローチは(私は私が間違っているの何を聞いて、その場合には)実現可能であるし、そうでない場合場合に

私の質問は、何より良い方法があるのですか?

例えば、あなたの関数からポインタを渡すと、関数名は、そうすることは明白であるようなものである場合を除き、それを削除するために知っているユーザーを期待するトラブルを求めている
+0

あなたはArchiveクラスの実装を表示していません。私はstd :: ios :: binaryでistreamを開きますか? – Benj

+0

私はここで書いたコードでstd :: ios :: binaryを忘れましたが、それは私のバージョンにあります。 istreamはifstreamから構築され、そのストリームは上記のようにstd :: ios :: binaryで開かれます。 – Merigrim

答えて

2

欠陥は、以下のとおりです。

  1. あなたはヒープメモリを割り当て、あなたの関数のいずれかから、それへのポインタを返すされています。これによりメモリリークが発生する可能性があります。あなたは今のところ漏れに問題はありませんが、クラスを設計する際にはそのようなことを覚えておく必要があります。
  2. ArchiveおよびFileFormatクラスを扱う場合、ユーザーは常にアーカイブの内部構造を考慮する必要があります。基本的には、データのカプセル化の考え方を妥協します。

クラスフレームワークのユーザーがアーカイブオブジェクトを作成するとき、彼はちょうどいくつかの生データへのポインタを抽出する方法を得ます。次に、この生データを完全に独立したクラスに渡す必要があります。また、FileFormatの複数の種類があります。このようなシステムを扱う漏れたヒープ割り当てを監視する必要がなくても、エラーが起こりやすくなります。

タスクにいくつかのOOP原則を適用しようとします。あなたのアーカイブオブジェクトは、異なるフォーマットのファイルのコンテナです。アプリケーション内のアーカイブの使用を簡素化することがクラスの

//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が格納されていて、64バイトではなく65バイトを読み込んだ場合、次のintを読み込むと迷惑メールになります。また、アラインメントとエンディアンの問題が発生することがあります(アプリケーションが複数のプラットフォームで実行される場合は、後者が重要です)。それでも、良いクラスデザインは、そのような問題に対処するより良いコードを作成するのに役立ちます。

+0

私はこの方法が本当に好きです!一度試してみると、ここにもう一度書きます。それがうまくいくなら、私はあなたの答えを受け入れます。 – Merigrim

+0

これを実装している間に私は1つの質問があった。必要になるまでデータをメモリに読み込みたくない場合はどうすればいいですか?たとえば、2つのファイルを持つアーカイブがある場合は、今は必要ですが、もう1つはまれな状況でのみ必要です。このようなシステムでやるのは簡単でしょうか?ひどいメモリオーバーヘッドではありませんが、それは後で重要になる可能性があります。 – Merigrim

+0

別の質問:ifstreamをファイルクラスに渡すと、ファイル内の絶対的なシークが困難になります。 fs.seekg(offset + x)を行うことができるように、またはオフセットがより良い方法があるように、それぞれのオフセットで初期化する必要がありますか? – Merigrim

2

createという単語で始まる関数。

ので

Foo * createFoo(); 

は、ユーザーが削除する必要がありますオブジェクトを作成する機能である可能性が高いです。

好ましい解決策は、最初にstd::vector<char>を返すか、ユーザーがstd::vector<char> &を関数に渡し、必要に応じてそのサイズを設定してバイトを書き込むことです。 (同じバッファを再利用できる複数の読み込みを行う方が効率的です)。

また、const-correctnessも学ぶ必要があります。

あなたの「しばらくしてからは迷惑メールでいっぱいです」では、どこでファイルの終了をチェックしますか? OPのコードの

+0

私はあなたが今書いたものを調べています。ジャンクデータに関しては、アーカイブを読むときにEOFに到達することはありません(ファイルエントリを読み込み、seekg(offset)を使用し、指定された長さのデータを読み取り、次にkg(orig_pos)を参照します)。この問題は、アーカイブオブジェクトを作成して(メモリにファイルを読み込み)、すぐに削除すると発生します。このプロセス中に(顕著な)メモリリークが発生していないことに注意してください。 – Merigrim

+0

これは悪いアドバイスではありませんが、元の質問でOPを助けることはまずありません。また、ベクトルを返すのは、C++ 11の場合、移動を効率的に行うためのアドバイスにすぎません。ベクトル&を渡すもう1つのオプションは、どのC++実装でも良いオプションになります。 – Benj

+0

OPの実際の問題を見るのは難しかったですが、メモリーの問題はあまりにも多くのメモリー管理を必要とすることに起因する可能性が高いと推測しましたが、事前の知識がなくてもジャンクの結果はバッファーを割り当てたバイトを読み込みます(EOFに達した場合)。 – CashCow

関連する問題