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

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

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

  • 제안의 명칭: Delegating Constructors
  • 제안자: H. Sutter, F. Glassborow
  • 관련 문서: N1581, N1618, N1895, N1986

이 제안은 생성자의 초기화 목록 부분에 다른 생성자의 호출 구문을 둘 수 있게 함으로써 공통적인 생성 임무를 다른 생성자에게 위임하게 만들자는 것입니다. 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 포럼에 이런 코드가 생각대로 작동하지 않는다는 질문이 올라온 적이 있습니다. 위임 생성자가 도입된다면 초보자들이 이런 실수를 저지르는 경우가 줄어들 것입니다.

top
TAG ,
트랙백 1 : 의견 # + 4

Trackback Address :: http://occamsrazr.net/tt/trackback/200

  1. Tracked from yesarang's me2DAY 2008/08/21 23:48 DELETE

    Subject: 김윤수의 생각

    C++0x 미리보기 8, 위임 생성자: 류광님의 C++0x 연재물입니다. 오랜만에 올리셨네요. ^^
comments powered by Disqus

(2013년 11월 10일자로 블로그에도 DISQUS 시스템을 도입했습니다. 기존 의견의 수정, 삭제, 댓글 추가는 여전히 가능합니다.)

  1. 김윤수 2008/08/21 23:47 PERMALINKMODIFY/DELETE REPLY

    복잡한 클래스 초기화 과정 설계할 때 골치아팠는데 잘 됐네요 ^^




  2. 류광 2008/08/22 16:56 PERMALINKMODIFY/DELETE REPLY

    글을 쓰고 나서... 멤버 변수들의 초기화를 편하게 한다는 측면에서는 제안과 반대 방향으로 위임을 지정하는 방식은 어떨까 하는 생각도 들었습니다. 이를테면:

    X() : i_(0), d_(0.0) { /* 1 */ }
    X(int i) : i_(i), X() { /* 2 */ } // X()의 i_(0)은 억제됨

    X x(1)의 경우 X(int)의 i_(1) => X()의 d_(0.0) => /* 1 */ => /* 2 */이 실행되는 형태이고요. X()에서는 i_가 이미 초기화되었으므로 i_(0)은 생략하게 하는 식입니다.

    그냥 문득 든 생각이었습니다...

  3. 염원영 2008/08/27 02:31 PERMALINKMODIFY/DELETE REPLY


    이미 C#에서는 이 방법이 사용되고 있는데...
    C++에서는 언제쯤 지원이 될런지...



  4. ohyecloudy 2008/09/13 13:56 PERMALINKMODIFY/DELETE REPLY

    지원하지 않아 초기화 하는 함수를 만들곤 했습니다. 흐~ 이제 더 편해지겠군요. @_@