|
|
|
|
그냥 냠냠쩝쩝 멕여보아요
스프링노트도 괜찮던데 거기로 옮길지도..
C++의 Stream에서 이진 파일 다루기 C++의 iostream library를 이용하여 binary를 다루다가 몇 가지 삽질로 인해서 고생하야 되새김질도 할 겸 적어보았습니다.
플래그에 ios::binary를 꼭 추가한다. 필수입니다.. 이것때문에 엄청나게 삽질을 했죠. 분명이 읽은 대로 출력을 했는데 나오는 게 다르니.. 저것을 추가하지 않으면 char 단위로 읽고써도 못 읽거나 더해지는 문자가 나옵니다.
입출력은 read(), write()만을 이용한다. operator <<, operator >>와 이것들을 이용하는 함수를 사용하면 안 됩니다. 특히, ostream_iterator 혹은 istream_iterator는 내부적으로 operator <<, operator >>를 사용하기 떄문에 사용해서는 안 됩니다.
저는 아래 예제처럼 편안히 가고 싶었는데 결과가 제대로 나오지 않아 그 아래 예제로 바꿨습니다.
void foo( istream &is, ostream &os ) { transform( istream_iterator< char >( is ), istream_iterator< char >(), ostream_iterator< char >( os ), Decode() ); }
void bar( istream &is, ostream &os ) { char encoded[1024], decoded[1024]; while( is.read( encoded, 1024 ) ) { transform( encoded, encoded + is.gcount(), decoded, Decode() ); os.write( decoded, is.gcount() ); } transform( encoded, encoded + is.gcount(), decoded, Decode() ); os.write( decoded, is.gcount() ); }
istream::gcount()가 있습니다. 네트워크 시간에서였던가
iostream library의 함수에서는 read를 해도 얼마나 내가 read를 했는지 알 수가 없습니다.
라는 말을 어디선가 들어서 처음엔 C-Runtime Library( fopen(), fread(), ... )를 쓰려고 했습니다만 잘 찾아보니 istream::gcount() 가 존재하더군요. 마지막으로 행한 Unformatted input operation에서 읽어들인 글자의 수를 뱉습니다. C Runtime library는 쓰기 싫어서 발버둥치다가 발견했네요.
istreambuf를 이용하십시요. istreambuf는 스트림 객체를 직접 건드려서 가져옵니다. 굳이 read, write를 이용할 필요가 없습니다. 쓰지 마십시오. 전혀 데이터의 손실 없이 글자 그대로 읽어옵니다. binary를 건드릴 때에는 istreambuf, ostreambuf를 이용하기만 하면 됩니다. 아래는 그 예제입니다.
void foo( istream &is, ostream &os ) { transform( istreambuf_iterator< char >( is ), istreambuf_iterator< char >(), ostreambuf_iterator< char >( os ), Decode() ); }
뭐 많이 있을 거 같았는데 써보니까 별 거 없네요. 그냥 삽질 많이해서 한 번 정리해봤습니다. 제목이 너무 거창한가 ?
갬블 : http://cartoon.stoo.com/gamble/html/ 체이서 : http://isplus.joins.com/enter/cartoon/73/cartoon_73.html 스투에서 전문으로 연재하시던 분이 이제는 일간스포츠로도 영역을 넓히셨군요 !! 갬블에 이어서 초 기대중입니다 ! 특히나 400번째 여자 이후로 연애물(?)인 듯하여 더욱 기대가 되네요 ! 초반부터 전개가 마음에 들었습니다. 400번째 여자에서 보여주던 진행이랑 비슷하게 흘러가는 것 같네요. ( 물론, 여기서 주제는 좀 다른듯 합니다. 사랑을 목표하는 내용은 아닌듯.. ) 400번째 여자의 주인공, 이영희 씨가 등장했네요. 이 이름 보는순간 깜짝 놀랐습니다. 400번째 여자의 후속작이구나 싶어서..! 400번째 여자 다시 봐야겠습니다. 이영희의 나이가 몇살이었는지, 그리고 육교에서 만난 그녀가 있었는지. 역시 화끈합니다. >_< 원래는 400번째 여자를 소장하려고 했으나 심의에 걸려서 발행이 불가능하게 되었다는 게 너무 안타까웠는데, 이번 작품은 꼭 소장할 수 있도록 책으로 나왔으면 좋겠네요. 김세영 스튜디오 화이팅임다 ! ㅎㅎ
 사실 왠간한 거 Visual Studio에도 다 있습니다. 그 중에서 가장 친구들이 최고로 꼽는 것이라면 역시, 자동완성 기능이겠죠. 하지만 자동완성은 IDE 상에서 (Ctrl+Space)로 이미 존재를 합니다. 물론, 그 기능은 덜 완벽하지만 덜 무거워서 그걸 더 선호하게 되네요. 그래서 사실 VisualAssist를 써야될 이유를 느끼지 못하고 있었습니다. 하지만, 근래와서 그 강력함을 몇가지 깨닫게 되었는데..! 그것들을 한 번 정리해보고자 합니다 ! Visual Assist X의 강점 !!Colorscheme
처음에 Visual Assist를 쓸 수밖에 없었던 이유입니다. 이 기능을 포기하기 어려웠기에 한번 접한 뒤로는 끊을수가 없었습니다. (크흑ㅠ) IDE 보다 네 경우에 대해 컬러링(?)을 해 줍니다.
- Classes, structures and typedefs
- Variables
- Preprocessor macros
- Methods
안그래도 코드는 예술이다라는 철학을 가지고 있습니다. 예술에서 예쁘게 보여야하는 것은 당연 !! 형형색색의 시대에 좀 더 세분화된 색감은 저의 눈을 사로잡기에 충분했지요 !! 게다가.. 6.0에서는 지원하지 않는 색도 고를 수가 있었기 때문에.. 더욱 이용하게 되었습니다 ! -_-
Go to definition of ...( Alt+G )
사실 이 기능은 IDE에도 들어있습니다. 하지만, 이 기능을 쓸 수 밖에 없는 이유가 있습니다. 그것은..! 조낸 빠르다는 겁니다. 게다가 조낸 정확하다는 설정도 가지고 있죠. IDE에서 이 기능은 F12를 통해 동작합니다. MSVC에서는 여기서 bsc 파일을 만드느라 이래저래 삽질을 하죠. (리눅스로 치면 ctag 정도 되겠죠..?) 게다가 그다지 정확하지도 않고 누락된 경우도 많습니다. 하여간.. 한 번 맛들이면 빠져나올 수 없는 강력한 기능 중 하납니다.
.h, .cpp shifter( Alt+O )
이 기능은 정말 누구나 있었으면 하는 기능이죠. 헤더와 소스를 열나게 돌아다녀야 하는 C++ 프로그래머의 입장에서는 정말 필요한 기능 중 하나입니다. 어느새 손에 붙어있는 단축키를 볼 수 있을 겁니다.
Convert dot to ->
저는 사실 왠간한 교정은 별로 좋아하지 않습니다. 저에게 있어서 이 기능은 Visual Assist에서는 시어머니 같은 기능입니다. 특히 괄호 맞춰주는 이런 건 참 불편합니다. 그럼에도 불구하고 쓸만하다고 생각되는 교정이 몇 개 있는데 그게 바로 Convert dot to ->과 좀 더 강력한 Case 교정, 괄호 갯수 맞춰주기 등입니다. 특히 Case 교정같은 건 대문자가 난무하는 상수들 입력하기에 편하더군요. 일반 IDE에서는 조금만 치고 Ctrl+Space를 눌러줘야 합니다만, Visual Assist에서는 끝까지 치고 그냥 넘어가도 교정해주는 경우가 있습니다. 이게 특히 편하더군요. (예를 들면 d3dxmatrix를 입력하고 계속 진행해주면 D3DXMATRIX로 자동으로 바꿔주는 겁니다.)
이런 저런 이유로 한 번 쭉 나열해봤습니다. 그냥 제가 왜 쓰나도 적어보고 싶었고, 이런 단축키들도 알리고 싶었고 말이죠. 아예 MS에서 이 기능들을 기본으로 넣어줬으면 하는 작은 바램이.. ㅋㅋ 참고로, 외판원 아닙니다. ㄱ-
저도 예전에 생성지를 상당히 길게 짰습니다만.. ^^;
몇가지 이유로 생성자를 짧게 유지하고 있습니다 ~ 생성자 제작 시 제한사항
생성자는 반환값을 설정할 수 없습니다. 아무래도 생성이라는 것이 항상 되면 좋겠지만, 여러가지 경우에 있어 생성이 실패하는 경우도 많습니다. 이럴 경우 다른 함수의 경우 실패를 반환하면 되겠지만, 생성자의 경우에는 예외를 던질 수밖에 없습니다. 예외라는 것이
- 처리하기도 불편하고 (try-catch 구문)
- 그 자체의 비중도 좀 되고
- 예외에 안전한 코드가 아니게
됩니다.
예를 들면,
COpenFile::COpenFile( const string &filename ) { if( false == m_is.open( filename.c_str() ) ) throw "break!!"; }
이런식의 문법밖에는 쓸 수가 없죠.
생성자에 인자를 넣을 수 없는 경우가 있습니다. 이것도 꽤 제한사항이 될 때가 있는데요, 특히 문제가 되는 경우가 Singleton입니다. Singleton 중에서 가장 편하고 자주 쓰게되는게 바로 Meyer's Singleton입니다. 예를 들면 아래와 같습니다.
static Singleton &Singleton::GetInst() { static Singleton inst; return inst; }
int foo() { Singleton::GetInst().Gotcha(); }
이것과 같은 경우 생성자에 동적으로 필요한 다른 인자를 넣기에 불편합니다. 이전에는 Singleton을 초기화 하지 않고 이용하는 법을 강구해봤습니다만, 이런저런 이유로 명시적으로 초기화하도록 하고 있습니다.
생성자는 소멸자를 호출하지 않습니다. 이것이 문제가 되는 경우는, 소멸자에서 자원을 해제하도록 하였을 경우, 생성 중에 실패를 하였다면 그 단계까지의 자원 소멸은 모두 생성 중에 해 주어야 한다는 것입니다. 길어지면 길어질수록 각 단계마다 해제할 자원이 늘어날테니, 보기에도 좋지 않고 짜기도 힘들어집니다.
ResManager::ResManager() { if( m_pFile = ReadFromFile( "GreatTeacherOnizuka" ) ) throw "111"; if( m_pTexture = TexFromFile( "Azumaga Daewang" ) ){ delete m_pFile; throw "222"; } if( m_pNicotin = NicotinFromFile( "Runge-Cutta" ) ){ delete m_pFile; delete m_pTexture; throw "333"; } ... }
이 코드의 또다른 문제점은.. 니코틴을 얻어올 때 실패하고 m_pFile에서도 delete에 실패하면, m_pTexture는 영원히 해제할 기회를 잃는다는 것입니다.
생성자 내에선 가상함수가 동작하지 않습니다. 클래스의 생성 순서는 기본 -> 파생 순입니다. 따라서, 생성자에서 가상함수를 넣는 것은 불가능합니다.
class Base { Base(); virtual void OnInit() = 0; }
Base::Base() { // Initialize routine... OnInit(); }
class Derived : public Base { void OnInit(); }
이렇게 코드를 짠다고 해서, Derived::OnInit()가 Base::Base()에서 불리지는 않습니다. ( 방금 이런 삽질을 하고왔습니다. 커헑 )
생성자를 만들 때의 개인적인 원칙
위와 같은 이유로 생성자에 대해서는 저는 다음과 같은 원칙을 지키고 있습니다. 생성자에 대한 공식적인 원칙은 따로 없는 것으로 알고 있습니다.
- 생성자에서는 실패할 수도 있는 함수는 가능한 집어넣지 않는다.
- 생성자에서 해제가 필요한 자원은 가능한 얻지 않는다.
- 생성에 실패할 때 해당 객체가 정말로 필요 없어질 때에만,
예외를 반환한다.
- 생성자에서는 가상함수가 동작하지 않음을 염두에 둔다.
물론, 소멸자에도 위와 동등한 원칙을 지키려고 하고 있습니다. 하지만, 생성자-소멸자를 이용한 자원 관리가 워낙에 편한 녀석이라 꼭 잘 지켜지지만은 않는게 문제네요. ^^; 생성자 및 소멸자에 대한 내용이 Effective C++에 정말 자세하게 나와있습니다. C++을 이용할 생각이라면 이 책 하나는 꼭 읽어보시길 강력하게 추천합니다. ^^ 위의 내용은 지극히 개인적인 내용이지만 꽤 많이 생각해본 문제이기에 이렇게 장황하게 늘어봤습니다. ^^
- C를 기본으로 발전시킨 언어이다.
- 절차적 프로그래밍( C의 기본 )
- 변수의 산술 연산
- 조건검사 및 루프
- 포인터와 배열
- 모듈화 프로그래밍( C에서 발전된 것, 데이터 은닉 )
- 데이터 추상화를 지원한다.
- 유사 타입
- 사용자정의 타입
- 구체 타입
- 추상 타입
- 가상 함수
- 객체 지향 프로그래밍을 지원한다.
- 클래스 계통( 상속, 다형성 )
- 일반화 프로그래밍을 지원한다.
- 컨테이너
- 일반화 알고리즘
- The C++ Programming Language, Bjarne Stroustrup
C++은 사용자 정의 타입을 만들 수 있는 클래스 개념 및 클래스 계통 개념을 가지고 있는데, 이 개념들은 시뮬라 언어에서 차용해 온 것이다. 뿐만 아니라, 프로그래머가 구상한 개념과 응용프로그램에서 쓰일 개념을 모형화하는 데 클래스를 사용해야 한다는 시스템 설계적인 아이디어 역시 시뮬라에서 가져온 것이다. C++에는 이러한 설계 철학을 바로 구현할 수 있도록 관련 문법 기능이 준비되어 있다. 이 말을 바꾸어 보면, C++을 효과적으로 구사하려면 이러한 기능을 제대로 사용해야 한다는 것과도 일맥상통한다. 프로그래밍 스타일은 재래식을 고수하면서 표현방법만 C++ 문법을 끌어와 사용하는 것은 C++의 위력을 제대로 쓰지 못하고 묻어 버리는 일이다.
- The C++ Programming Language, [ 비야네 스트롭스트룹 ]
취지여러분들은 한 계통( 기본 클래스와 그의 파생 클래스들 )의 객체를 다룰 때, 특정 클래스에 대한 처리가 필요하다고 할 때, 어떤 방식으로 처리하시나요? 배경어떤 3D의 공간에서 하나의 객체를 나타내기 위한 클래스 class Object가 있습니다. 이 객체는 인터페이스에 가깝지만, 순수 가상함수는 아닙니다. 이 함수는 다음의 함수들을 지니고 있습니다. - virtual void Object::Update( const double );
- virtual void Object::Render();
- virtual const bool Object::IsCollide( const Object & );
- virtual void Object::OnCollide( const Object & );
이 클래스를 부모로 삼는 클래스들은 위의 함수들을 매 메인루프때마다 불러줘야 합니다. 메인루프는 모든 오브젝트를 지니고 있으며, 다음과 같은 형태로 계속 실행됩니다. namespace Digitz { typedef std::vector< Object * > VecObject; VecObject vObject; void MainLoop( const double delta ) { for( size_t i= 0; i< vObject.size(); ++i ){ vObject[i]->Update( delta ); vObject[i]->Render(); for( size_t j= 0; j< vObject.size(); ++j ) if( i != j && vObject[i]->IsCollide( vObject[j] ) ) vObject[i]->OnCollide( vObject[j] ); // (1) } } };
이제 그의 자식인 class Slime : public Object이 있습니다. 이 친구는 다른 객체랑 충돌하면 자신의 다른 객체 하나를 또 생성합니다. 이 객체는 class SlimeFactory에서만 나올 수 있습니다. 위치정보를 class SlimeFactory가 가지고 있기 때문이지요. 이 함수는 const bool Slime::IsCollide( const Object & )에서 반환값이 참이 되면 VecObject vObject에 새로운 Slime을 추가해주어야 합니다. 자, 이런 상황에서 이런 특성화되어있는 동작을 어떻게 처리해야 할까요 ? 대안위의 경우에서 제가 생각한 대안은 아래 두 가지가 있습니다.
Dynamic casting을 이용한다.
캐스팅을 통해 (1) 부분을 다음과 같이 처리합니다.
//(1) { vObject[i]->OnCollide( vObject[j] ); if( Slime *s = dynamic_cast< Slime * >( vObject[j] ) ) vObject.push_back( SlimeFactory::GetInstance()->CreateSlime() ); }
이 방식은 나름대로 깔끔하지만, dynamic_cast를 위해 런타임정보를 가져야만 되어서 프로젝트 전체에 부하를 주게 됩니다. 특히 Visual C++에서 dynamic_cast는 컴파일러 옵션 /GR을 해 주어야되서 쓰기가 좀 껄끄럽지요.
자신의 타입을 나타내는 특정한 ID를 발급하게 한다.
class Object 내부에 virtual const int GetType() const;을 둡니다. class Object를 상속하는 모든 클래스는 저 함수를 새로 작성하게 해서, Slime일 경우
//(1) { vObject[i]->OnCollide( vObject[j] ); if( TYPE_SLIME == vObject[j]->GetType() ) vObject.push_back( SlimeFactory::GetInstance()->CreateSlime() ); }
와 같이 처리하는 것이죠.
이 방법은 따로 타입에 대한 정보를 사용자가 마련하므로, 컴파일러에게 다른 부하를 주지는 않습니다만, Slime을 상속한 녀석들에게 적용되지는 않는 단점이 있습니다. SucreamSlime, BashiSlime같은 녀석들은 각각 TYPE_SUCREAMSLIME, TYPE_BASHISLIME을 쓰게될테니 말이죠.
Slime용 벡터를 따로 둔다.
이 방법은 vector< Object * > 대신 따로 vector< Slime * > vSlime을 두어서, vSlime[i]->Render(), vSlime[i]->Update()를 호출하게 하는 것이죠. 이 방식은 다른 어떤 것보다 효율이 좋겠지만, 이렇게 특별한 처리가 필요한 객체가 생길때마다 따로 저장공간과 처리를 마련해주어야만 합니다.
논의위와 같이 세 가지 대안을 생각해보았고, 결과적으로 두번째 대안,
" 자신의 타입을 나타내는 특정한 ID를 발급하게 한다. " 를 선택하게 되었습니다. 여러분들은 여기서 어떤 대안을 선택하실지, 혹은 다른 대안을 가지고 계신지 궁금합니다. 결론이것을 봐 줘. 이것을 어떻게 생각해?
|
|
|