C++0x 미리보기 8, 위임 생성자

Twitter icon류광, 2008-08-21 16:08
클래스 생성자들의 코드 중복을 줄일 수 있는 방법 하나.

변명 한마디: 적어도 한 달에 한 번은 C++0x 미리보기를 올리려고 했는데 지난 글 이후로 어느덧 두 달을 넘어 세 달째로 가고 있습니다. 작은 것은 작다고 안 쓰고 큰 것은 커서 못 쓰고 있었던 게 아닌가 합니다... 페이스를 되찾을 때까지는 그냥 제가 이해하는 한에서, 제가 흥미를 느끼는 부분만 간단하게 써 나가겠습니다(사실 지금까지도 그래왔지만요). 어쨌든 날림이라고 너무 욕하지 마시길!

이 제안은 생성자의 초기화 목록 부분에 다른 생성자의 호출 구문을 둘 수 있게 함으로써 공통적인 생성 임무를 다른 생성자에게 위임하게 만들자는 것입니다. N1986에는 다음과 같은 예제가 있습니다(화면 공간 문제로 코드 포매팅과 함수 이름들을 약간 수정했음).

class X {
  X( int, W& );
  Y y_;
  Z z_;
public:
  X();
  X( int );
  X( W& );
};

X::X( int i, W& e ) : y_(i), z_(e) 
{ /*공통적인 초기화*/ }
X::X()              : X( 42, 3.14 ) // X::X(int, W&)에게 위임
{ SomePostInit(); } // 추가적인 초기화
X::X( int i )       : X( i, 3.14 ) // X::X(int, W&)에게 위임
{ OtherPostInit(); } // 또 다른 추가 초기화
X::X( W& w )        : X( 53, w ) // X::X(int, W&)에게 위임
{ /* 추가적인 초기화 없음 */ }
X x( 21 ); // y_나 z_의 생성자가 예외를 던지면 X::~X가 호출됨

현재는 이런 코드를 작성해야 합니다.

X::X( int i, W& e ) : y_(i), z_(e) 
{ CommonInit(); } // 어떤 공통의 초기화 메서드를 호출
X::X()              : y_(42), z_(3.14) 
{ CommonInit(); SomePostInit(); }
X::X( int i )       : y_(i), z(3.14) 
{ CommonInit(); OtherPostInit(); }
X::X( W& w )        : y_(53), z(w) 
{ /* 추가적인 초기화 없음 */ }

이 경우에는 CommonInit();의 호출이 중복되며, 매번 멤버 초기화 구문을 써주어야 한다는 것도 사소하지만 짜증스러운 일입니다(이 예에서는 예를 들어 y_(42), z_(3.14)X(42, 3.14)의 차이가 크지 않지만, 멤버 변수들이 더 많다면 차이가 좀 더 커질 것입니다). 멤버 초기화 구문의 반복 문제를 이를테면 다음과 같은 방식으로 피하는 것은 바람직하지 않습니다.

void X::CommonInit() 
{ y_ = 42; z_ = 3.14; /* 그 외의 초기화 */}
X::X( int i )
{ CommonInit(); y_ = i; OtherPostInit(); }

X(int) 안의 y_ = i;는 멤버의 초기화가 아니라 일단 초기화된 멤버의 재설정입니다. 이러한 차이가 미치는 영향은 Y가 단순한 내장 형식이 아니라 덩치 큰 객체라면 더욱 커집니다.

위임의 연쇄, 즉 위임된 생성자가 다른 생성자에게 초기화를 위임하는 것도 가능합니다. 다음이 그런 예입니다.

X(int i, double d, string s) : i_(i), d_(d), s_(s) { ... }
X(int i, double d) : X(i, d, "") { ... } // X(int, double, string)에게 위임
X(int i) : X(i, 0.0) { ... } // X(int, double)에게 위임

그 외에도 함수 try 블록이라던가 템플릿 생성자 등에 관련된 세부 사항들이 있는데, 여기서 더 이야기하지는 않겠습니다. 그런 부분들과 더 많은 예제, 그리고 표준 문구 수정안이 N1986에 나와 있습니다.

참, C++을 다른 사람들에게 가르치는 입장에 있는 분이라면 이런 예제가 더 흥미로울지 모르겠습니다(역시 N1986에서 발췌):

class X {
  int i_;
public:
  X();
  X( int );
};
X::X() { DoSomethingObservableToThisObject(); }
X::X( int i ) : i_(i) { X(); } // 컴파일 오류는 아니나 무의미

이 코드의 의도는 X(int)에서 i_를 직접 설정할 뿐만 아니라 기본 생성자 X()의 또 다른 어떤 행동을 수행하겠다는 것이겠지만, X(int) 안의 X();는 그냥 임시 객체를 생성했다가 폐기하는 것일 뿐입니다. 이런 실수를 누가 하겠냐고 생각하는 분도 있겠지만, 실제로 GpgStudy 포럼에 이런 코드가 생각대로 작동하지 않는다는 질문이 올라온 적이 있습니다. 위임 생성자가 도입된다면 초보자들이 이런 실수를 저지르는 경우가 줄어들 것입니다.

태그: C++ C++0x

comments powered by Disqus

예전 댓글(읽기 전용)