본문 바로가기

프로그래밍/C++

생성자 제작 시 제한사항

생성자 제작시 제한사항

  • 생성자는 반환값을 설정할 수 없습니다.

    아무래도 생성이라는 것이 항상 되면 좋겠지만, 여러가지 경우에 있어 생성이 실패하는 경우도 많습니다. 이럴 경우 다른 함수의 경우 실패를 반환하면 되겠지만, 생성자의 경우에는 예외를 던질 수밖에 없습니다. 예외라는 것이
    1. 처리하기도 불편하고 (try-catch 구문)
    2. 그 자체의 비중도 좀 되고
    3. 예외에 안전한 코드가 아니게
    됩니다. 예를 들면,
    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++을 이용할 생각이라면 이 책 하나는 꼭 읽어보시길 강력하게 추천합니다. ^^