2012-01-05 2 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(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; 
} 

나는이 방법의 단순성을 좋아하지만 나는 현재 메모리 어의 많은 어려움을 겪고있어 : 이진 데이터를 읽을 때

, 나는 다음과 같은 기능을 가진 BinaryReader 클래스를 사용 rors와 SIGSEGVs 그리고 나는이 방법 때문에 그것이 두렵다. 예를 들어 루프에서 반복적으로 아카이브를 만들고 읽는 경우를 예로들 수 있습니다. 많은 수의 반복 작업을 처리하지만 잠시 후 정크 데이터 읽기를 시작합니다.

이 접근법이 가능한 경우 (내 경우 내가 잘못한 것을 묻습니다), 그렇지 않은 경우 더 나은 접근 방법이 있습니까?

+0

당신은 Archive 클래스의 구현을 보여주지 않았기 때문에 std :: ios :: binary로 istream을 여는 것으로 추정 할 수 있습니까? – Benj

+0

나는 여기서 작성한 코드에서 std :: ios :: binary라는 것을 잊어 버렸지 만, 나의 버전에는 거기에있다. istream은 ifstream에서 생성되며 스트림은 위에 표시된 것처럼 std :: ios :: binary로 열립니다. – Merigrim

답변

2

결함은 다음과 같습니다 당신은 힙 메모리를 할당하고 기능 중 하나에서 포인터를 반환하는

  1. . 이로 인해 메모리 누수가 발생할 수 있습니다. 누출 문제는 없지만 수업을 설계하는 과정에서 이러한 것들을 염두에 두어야합니다.
  2. Archive 및 FileFormat 클래스를 처리 할 때 사용자는 항상 아카이브의 내부 구조를 고려해야합니다. 기본적으로 데이터 무분별 개념에 위배됩니다.

클래스 프레임 워크 사용자가 Archive 객체를 만들면 그는 원시 데이터에 대한 포인터를 추출하는 방법을 얻습니다. 그런 다음 사용자는이 원시 데이터를 완전히 독립적 인 클래스로 전달해야합니다. 또한 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

이것을 구현하는 동안 나는 하나의 질문이있었습니다. 필요할 때까지 데이터를 메모리에 읽지 않으려면 어떻게해야합니까? 예를 들어, 두 개의 파일이있는 아카이브가있는 경우 하나는 필요하지만 다른 하나는 드문 경우에만 필요합니다. 이런 종류의 시스템으로하기가 쉬울까요? 끔찍한 메모리 오버 헤드는 아니지만 나중에 중요한 의미를 가질 수 있습니다. – Merigrim

+0

또 다른 질문 : ifstream을 파일 클래스에 전달하면 파일에서 절대 탐색이 어려워집니다. fs.seekg (offset + x)를 수행 할 수 있도록 더 좋은 접근 방법이 있습니까? – Merigrim

2

함수 이름이 명백하지 않은 경우 함수에서 포인터를 전달하고 사용자가이를 삭제할 것을 알기를 기대합니다. create라는 단어로 시작하는 함수.

그래서

Foo * createFoo(); 

는 사용자가 삭제해야 객체를 생성하는 기능이 될 가능성이 높습니다.

우선 해결책은 std::vector<char>을 반환하거나 사용자가 std::vector<char> &을 함수에 전달하고 필요한 경우 크기를 설정하는 것이 좋습니다. (동일한 버퍼를 재사용 할 수있는 다중 읽기를 수행하는 것이 더 효율적입니다.)

또한 const-correctness도 알아야합니다.

"잠시 후 정크로 가득 차 있습니다"라고 말하면 파일 끝을 어디에서 확인합니까? 영업 이익의 코드

+0

나는 지금 당신이 쓴 것을보고 있습니다. 정크 데이터의 경우 아카이브를 읽을 때 EOF에 도달하지 않습니다 (파일 항목을 읽고 seekg (오프셋), 지정된 길이의 데이터를 읽은 다음 seekg (orig_pos)). 문제는 내가 파일을 메모리로 읽어 들이고 아카이브 객체를 생성하고 즉시 삭제할 때 발생한다. 이 과정에서 메모리 누수가 발생하지 않습니다. – Merigrim

+0

이것은 나쁜 충고는 아니지만 원래 질문으로 OP를 도우려는 것은 아닙니다. 또한 벡터 을 반환하면 이동 구성으로 효율적인 작업을 수행 할 수있는 C++ 11의 좋은 조언 일뿐입니다. 벡터를 전달하는 다른 옵션은 모든 C++ 구현에서 좋은 옵션이 될 것입니다. – Benj

+0

OP의 실제 문제를보기가 어려웠습니다. 메모리 문제가 너무 많아서 메모리 관리가 필요하지 않아서 생기는 것 같았습니다. 사전 지식 없이도 정크 결과는 버퍼를 할당했지만 실패했을 수 있습니다. 바이트를 읽습니다 (EOF에 도달 한 경우). – CashCow