Abseil Tip 74 위임 생성자와 상속 생성자

원래 2014-04-21에 totw/74로 게시됨
작성자: Bradley White (bww@google.com)

“위임 작업이 효과를 발휘하려면, 위임하는 사람도 함께 일해야 합니다.” – 로버트 하프

클래스에 여러 생성자가 있는 경우, 각 변형에서 유사한 초기화를 수행해야 할 필요가 자주 발생합니다. 코드 중복을 피하기 위해, 많은 기존 클래스는 SharedInit()이라는 private 메서드를 정의하고, 생성자에서 이를 호출하는 방식을 사용합니다. 예를 들어:

class C {
 public:
  C(int x, string s) { SharedInit(x, s); }
  explicit C(int x) { SharedInit(x, ""); }
  explicit C(string s) { SharedInit(0, s); }
  C() { SharedInit(0, ""); }
 private:
  void SharedInit(int x, string s) {  }
};

C++11은 이러한 상황을 더 명확하게 처리하기 위해 새로운 메커니즘인 위임 생성자(delegating constructors)를 제공합니다. 이를 통해 한 생성자를 다른 생성자 기반으로 정의할 수 있습니다. 클래스에 기본 초기화가 비싼 멤버가 있는 경우, 이 메커니즘은 효율성 측면에서도 이점을 제공합니다.

class C {
 public:
  C(int x, string s) {  }
  explicit C(int x) : C(x, "") {}
  explicit C(string s) : C(0, s) {}
  C() : C(0, "") {}
};

위임 생성자를 사용하는 경우, 멤버 초기화 목록을 동시에 사용할 수 없다는 점에 유의하세요. 모든 초기화는 위임받은 생성자가 처리합니다. 그러나 과도하게 사용하지 마세요. 공유 코드가 멤버를 설정하는 것뿐이라면, 별도의 멤버 초기화 목록이나 클래스 내 초기화가 위임 생성자보다 더 명확할 수 있습니다. 적절한 판단을 사용하세요.

참고: 객체는 위임 생성자가 반환될 때까지 완전한 상태로 간주되지 않습니다. 실제로, 이는 생성자가 예외를 던질 경우에만 중요합니다. 예외가 발생하면 위임된 객체는 불완전한 상태로 남게 됩니다.

다소 드문 생성자 코드 중복은 멀티 생성자 클래스를 래퍼로 확장할 때 발생합니다. 예를 들어, 클래스 C를 감싸고 새로운 멤버 함수를 추가하는 “표면적(veneer)” 하위 클래스를 생각해보세요.

class D : public C {
 public:
  void NewMethod();
};

그렇다면 D의 생성자는 어떻게 할까요? 모든 전달용 보일러플레이트 코드를 작성하는 대신, C의 생성자를 재사용하고 싶을 것입니다. C++11은 상속 생성자(inheriting constructors)라는 새로운 메커니즘을 통해 이를 지원합니다.

class D : public C {
 public:
  using C::C;  // C의 모든 생성자를 상속합니다.
  void NewMethod();
};

생성자에 대한 이 새로운 “using” 형식은 멤버 함수에서의 기존 사용 방식과 유사합니다.

그러나 생성자는 파생 클래스에 새 데이터 멤버가 추가되지 않거나, 새 멤버가 명시적으로 초기화될 필요가 없는 경우에만 상속되어야 한다는 점을 유의하세요. 실제로 스타일 가이드는 새 멤버(있는 경우)가 클래스 내 초기화를 가지지 않는 한 생성자 상속을 지양하도록 권장합니다.

C++11의 위임 생성자와 상속 생성자를 사용하여 코드 중복을 줄이고, 전달 보일러플레이트를 제거하며, 클래스를 더 간단하고 명확하게 만드세요.