텍스트 파일을 읽기 편하게 하기 위한 클래스를 만들어 보자. 간단한 사용 예는 다음과 같다.
reader r;
std :: set < std :: string > all_compounds;
while ( r( "compounds_in_treatment.txt" , '#' , '\t' )){
all_compounds. insert( r. begin ()+ 2 , r. end ());
}
목적은 단순했다. while 문 안에 저렇게 해서 텍스트 파일을 한 줄씩 읽어들일 수 있게 하기 위한 것이었다. 더 이상 읽을 줄이 없거나 아니면 아예 애초에 파일을 열지 못했으면 false 가 반환되기 때문에 위처럼 해서 모든 줄을 읽을 수 있다. while 에 걸린 줄에 보면 첫 번째 인자는 파일 이름이고, 두 번째는 주석으로 간주할 줄이 제일 첫 칸에 어떤 문자가 있는지를 지정해 주는 것이고, 세 번째는 만약 텍스트 파일의 각 줄을 읽어서 tab 으로 자르고 싶으면 저렇게 해주면 되는 것이다. begin 이나 end 는 tab 으로 잘린 문자열 리스트를 vector 로 저장하고 있는데 그 벡터의 begin 과 end 를 반환하게끔 되어 있다.
멤버 함수의 목록을 보자면 다음과 같다.
reader();
virtual ~ reader();
reader( const std :: string & file_name);
std :: string operator []( const int & index);
bool read_line( const char & comment_mark);
bool read_line( const char & comment_mark, const char & delimiter);
std :: vector < std :: string >:: const_iterator begin () const ;
std :: vector < std :: string >:: const_iterator end () const ;
const std :: string & get_line() const ;
const std :: string & operator ()() const ;
bool operator ()( const std :: string & file_name);
bool operator ()( const std :: string & file_name, const char & comment_mark, const char & delimiter);
bool close ();
위 코드를 분석해 보면, operator[] 나 operator() 를 어떻게 재정의 해서 편리하게 사용할 수 있는지 알 수 있을 것이다. 위의 사용 예에는 안 나왔는데,
과 같은 방식으로도 쓸 수 있다. 이것은, 읽은 줄을 delimiter 로 자른 후 두 번째 요소를 반환하는 것.
전체 source 는 다음에 있다.
전체 소스 보기 접기
class reader{
private :
std :: string _file_name;
std :: ifstream * _file ;
std :: string _line;
std :: vector < std :: string > _v;
public :
reader() : _file_name( "" ), _line( "" ), _file ( NULL ){
};
virtual ~ reader(){
if ( _file != NULL ){
delete _file ;
}
};
reader( const std :: string & file_name) : _file_name( file_name), _line( "" ), _file ( NULL ){
};
std :: string operator []( const int & index){
if ( index < _v. size ()){
return _v[ index];
}
else {
return "" ;
}
};
bool read_line( const char & comment_mark){
if ( _file == NULL ){
if ( _file_name != "" ){
_file = new std :: ifstream( _file_name. c_str());
if ( _file -> is_open() == false ) return false ;
bool reach_bottom = true ;
while ( std :: getline(* _file , _line)){
if ( _line[ 0 ] == comment_mark) continue ;
reach_bottom = false ;
break ;
}
if ( reach_bottom == true ){
close ();
return false ;
}
else {
return true ;
}
}
else {
return false ;
}
}
else {
bool reach_bottom = true ;
while ( std :: getline(* _file , _line)){
if ( _line[ 0 ] == comment_mark) continue ;
reach_bottom = false ;
break ;
}
if ( reach_bottom == true ){
close ();
return false ;
}
else {
return true ;
}
}
if ( _v. empty() == false ){
_v. clear();
}
return true ;
};
bool read_line( const char & comment_mark, const char & delimiter){
if ( read_line( comment_mark) == false ) return false ;
_v = split< std :: string , string2string>( _line, delimiter);
return true ;
};
std :: vector < std :: string >:: const_iterator begin () const {
return _v. begin ();
};
std :: vector < std :: string >:: const_iterator end () const {
return _v. end ();
};
const std :: string & get_line() const {
return _line;
};
const std :: string & operator ()() const {
return _line;
};
bool operator ()( const std :: string & file_name){
if ( _file == NULL ){
_file_name = file_name;
_file = new std :: ifstream( _file_name. c_str());
if ( _file -> is_open() == false ) return false ;
}
else {
if ( _file -> is_open() == true ){
_file -> close ();
}
_file -> open ( _file_name. c_str());
if ( _file -> is_open() == false ) return false ;
}
return true ;
};
bool operator ()( const std :: string & file_name, const char & comment_mark, const char & delimiter){
if ( _file_name != "" ){
if ( file_name != _file_name){
if ( _file != NULL ){
_file -> close ();
_file -> open ( file_name. c_str());
if ( _file -> is_open() == false ) return false ;
}
else {
_file = new std :: ifstream( file_name. c_str());
if ( _file -> is_open() == false ) return false ;
}
_file_name = file_name;
}
else {
}
}
else {
if ( _file != NULL ){
_file -> close ();
_file -> open ( file_name. c_str());
if ( _file -> is_open() == false ) return false ;
}
else {
_file = new std :: ifstream( file_name. c_str());
if ( _file -> is_open() == false ) return false ;
}
_file_name = file_name;
}
return read_line( comment_mark, delimiter);
};
bool close (){
_file_name = "" ;
if ( _file != NULL ){
_file -> close ();
delete _file ;
_file = NULL ;
}
_v. clear();
_line = "" ;
return true ;
}
};
접기
1시간도 안걸려 후딱 만든 거라 고칠 부분이 좀 있을 것으로 생각한다. 일단 메모리 확인은 해 봤는데, valgrind 로 확인해 보면,
==4349== HEAP SUMMARY:
==4349== in use at exit: 0 bytes in 0 blocks
==4349== total heap usage: 1,812,120 allocs, 1,812,120 frees, 157,495,929 bytes allocated
==4349==
==4349== All heap blocks were freed -- no leaks are possible
==4349==
==4349== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 6 from 6)
라고 나온다, 언제 봐도 기분 좋은, no leaks are possible, :)
이 클래스를 만들기 바로 전까지는 항상,
std :: ifstream hfile(file_name.c_str() );
std :: string line;
while ( std :: getline( hfile, line)){
if ( line == "" || line[ 0 ] == '#' ) continue ;
std :: vector < std :: string > v = split< std :: string , string2string>( line, '\t' );
std :: set < std :: string > cpd( v. begin () + 3 , v. end ());
compound_in_the_same_herb. push_back( cpd);
}
이런 식으로 코딩을 했었지... 저 부분을 좀 줄여 보고 싶었다. 항상 반복되던 부분. 더구나 파이썬을 쓰다 보면 파일 읽는 부분이 파이썬에 비해 약간 번거로웠고, 그 약간이 반복되다 보니 좀 그랬다. 그래서 파이썬만큼 편리하게 만들어 보고 싶었지, ㅋ.
그래, 이런 클래스를 만들고 싶었어, ㅋ. 텍스트 파일을 열 때마다 항상 같은 코드가 반복되어서 만들어야지만들어야지 라고 생각만 하고 있다가, 지난 주말에 낮잠을 자려다 갑자기 생각이 난 이후 머릿 속에서 코딩을 한 번 죽 해보고, 오늘 일을 하다 갑자기 또 생각이 나서 후다닥 만들어 버렸다, ㅋ.