C++0x 미리보기 9, 상속 생성자

Twitter icon류광, 2008-11-09 17:11
이번에는 부모-자식간 생성자 중복을 줄일 수 있는 방법 하나.

C++0x 미리보기 8, 위임 생성자에 이어 이번에도 생성자에 대한 내용입니다. ‘코딩 귀차니즘’ 차원에서는 위임 생성자보다 더 강력할 듯.

우선 동기 유발 예제를 보죠.

class B
{
    int i_;
public:
    B() : i_(0) {}
    B(int i) : i_(i) {}
};

class D: public B
{

};

D d1;     // (가) OK
D d2(10); // (나) 컴파일 오류

클래스 D에는 아무런 생성자도 정의되어 있지 않으므로 컴파일러가 기본 생성자를 자동으로 만들어 주며, 따라서 (가)는 무사통과합니다. 반면 (나)는 컴파일 오류인데, Dint를 받는 생성자가 없기 때문입니다.

int를 받는 생성자를 추가하면 (나)는 해결이 되나, 대신 컴파일러가 기본 생성자를 만들어 주지 않으므로 (가)가 컴파일 오류가 됩니다. 결국 두 생성자 모두 지정해 주어야 합니다.

class D: public B
{
public:
    D() {}
    D(int i) : B(i) {}
};

D d1;     // (가) OK
D d2(10); // (나) OK

이 예에서 D의 생성자들은 단지 B의 생성자를 호출하기만 하는데, 이 예가 아주 작위적이지는 않을 것입니다. 이 제안에 깔린 문제의식은, 저렇게 그냥 부모 생성자 호출 구문만 가진 생성자 정의를 사람이 일일이 코딩하는 것은 지루한 일이고 컴파일러가 더 잘할 수 있다는 것입니다.

각설하고, ‘부모의 생성자들을 상속하겠다’고 컴파일러에게 알려주는 목적으로 이 제안이 제시하는 구문은 다음과 같습니다.

class D: public B
{
public:
    using B::B; // 이겁니다.
};

이러면 D(int)가 암묵적으로 만들어 집니다.

( 잠깐 영어 공부... 이런 D(int)를 이 제안의 이름이기도 한 inheriting constructor라고 부릅니다. (부모의 것을)상속‘하는’ 생성자라는 뜻인데, 이 글에서는 줄여서 ‘상속 생성자’라고 칭하기로 하겠습니다. 처음에 제안 제목만 봤을 때에는 ‘생성자를 상속하다’를 명사화한 ‘생성자 상속하기’라는 뜻인 줄 알았는데, 제안서를 구체적으로 읽어 보면 상속이라는 행위를 나타내는 것이 아니라 특정한 종류의 생성자를 지칭하는 용도로 쓰이고 있음이 분명합니다. 이전 미리보기에서 말한 delegating constructor 역시 ‘생성자 위임하기’가 아니라 ‘위임하는 생성자’(줄여서 위임 생성자)였습니다. 제안서에는 inherited constructor(상속된 생성자)라는 표현도 나오는데, 수동태로 볼 수도 있고, 상속 절차가 끝났음을 뜻하는 과거 시제를 나타내는 목적일 수도 있겠습니다. 번역어 ‘상속된’ 역시 두 경우 모두 함의합니다. )

부모의 생성자를 ‘상속한다’라고 해서 부모의 생성자의 본문 코드 자체가 자식의 생성자에 복사되는 것은 아닙니다. 단지 멤버 초기화 목록에서 부모의 생성자에게 인수들을 전달하는 것일 뿐입니다. 예를 들어

class B
{
    int i_;
public:
    B() : i_(0) {}
    B(int i) : i_(i) { std::cout << i_ << "\n"; }
};

라고 할 때 D(int i)

    D(int i) : {
        i_ = i; // 사실 이것은 접근 위반
        std::cout << i_ << "\n"; 
    }

가 아니라 그냥

    D(int i) : B(i) {}

이 되는 것입니다. 일반화하자면

B(type1 arg1 [, type2 arg2, ... typen argn]);

에 대해

D(type1 arg1 [, type2 arg2, ... typen argn]) 
    : B(arg1 [, arg2, ... argn]) 
{}

이 만들어 지는 것이라고 저는 이해했습니다. 이러한 상속 생성자들이 D 자신의 멤버들에 대한 초기화와는 무관하다는 점도 주의할 필요가 있겠습니다. 즉,

struct D: B {
    using B::B;
    int x;
};

라고 할 때

D d(1);

라고 해도 d.x 는 초기화되지 않습니다.

제안서에는 자식이 어떤 부모 생성자들을 상속하는지, 그리고 상속하지 않는지에 대해 자세히 나와 있는데, 따지고 들면 좀 복잡할 수 있습니다. 몇 가지 예제로 맛만 보기로 하죠. 최신 제안서 N2540의 C++ 명세서 수정·추가안에 있는 예제들을 사용하겠습니다.

struct B1 {
  B1( int );
};

struct D1 : B1 {
  using B1::B1;
};

이 예에서, B1에 존재하는 생성자들은 다음과 같습니다.

좀 더 복잡한 예를 봅시다.

struct B2 {
  B2( int = 13, int = 42 );
};

struct D2 : B2 {
  using B2::B2;
};

B2의 생성자들은 이렇습니다.

B2( int = 13, int = 42 )이 인수 구성에 따라 세 가지로 세분화되었다는 점에 주목하시길. D2의 생성자들은 다음과 같습니다.

제안서 N2540에는 그 외에도 다중 상속 시 주의할 점 등 좀 더 자세한 사항이 나와 있는데, ‘[ Example: ’로 되어 있는 예제들을 중심으로 읽어 나가면 그리 어렵지 않게 이해할 수 있을 것입니다.

태그: C++ C++0x

comments powered by Disqus

예전 댓글(읽기 전용)