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

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

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

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에 존재하는 생성자들은 다음과 같습니다.

  • B1( const B1 & )
  • B1( int )

실제로 D1에 만들어지는 생성자들은 다음과 같습니다.

  • D1() - 암묵적으로 선언된 기본 생성자.
  • D1( const D1 & ) - 역시 암묵적으로 선언된 복사 생성자. B1의 것을 상속한 것은 아님.
  • D1( int ) - using B1::B1에 의해 상속된 생성자.

이 예에서 보듯이, 복사 생성자는 상속되지 않습니다. 그리고 D1()이 존재하긴 하나, B1()이 존재하지 않으므로 실제로 사용할 수는 없다는 점도 주의해야 할 것입니다. 즉,

D1 d;

는 컴파일 오류입니다.

좀 더 복잡한 예를 봅시다.

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

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

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

  • B2( const B2 & )
  • B2( int = 13, int = 42 )
  • B2( int = 13 )
  • B2()

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

  • D2() - 암묵적으로 선언된 기본 생성자. 상속된 것이 아님.
  • D2( const D2 & ) - 암묵적으로 선언된 복사 생성자. 상속된 것이 아님.
  • D2( int, int ) - 암묵적으로 선언된 상속 생성자.
  • D2( int ) - 암묵적으로 선언된 상속 생성자.

이 예에서 보듯이, 복사 생성자와 마찬가지로 기본 생성자는 상속되지 않습니다. 또한 기본 인수의 값은 상속되지 않습니다.

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

top
TAG ,
트랙백 0 : 의견 # + 2

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

comments powered by Disqus

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

  1. 임수서룬뫼 2008/12/29 10:21 PERMALINKMODIFY/DELETE REPLY

    흥미로운 변화가 계속되니 C++0x가 정말 기대되는군요.
    그런데, 'inheriting constructor'는 '생성자 상속하기'가 아닐까요?
    일단 '상속 생성자'라면 명백하게 생성자의 한 종류인데 일반적인 생성자와 확실히 다른 구문을 사용하니까요.
    (물론 '상속 생성자'라는 표현도 그다지 잘못되었다고 보지는 않습니다.)

  2. 류광 2008/12/29 16:12 PERMALINKMODIFY/DELETE REPLY

    중간의 '잠깐 영어 공부...' 부분에도 언급했듯이 원 제안에서는 특정 종류의 생성자를 지칭하는 용어로 사용하고 있습니다.

    그나저나 올해가 가기 전에 한 편 더 올렸으면 하는데 가능할지 모르겠습니다...