Abseil Tip 61 기본 멤버 초기화 (Default Member Initializers)


주간 팁 #61: 기본 멤버 초기화 (Default Member Initializers)


원래 게시일: 2013년 11월 12일 (TotW #61)
작성자: Michael Chastain (mec.desktop@gmail.com)
최종 업데이트: 2016년 10월


기본 멤버 초기화 선언하기

기본 멤버 초기화는 생성 시 멤버의 기본 값을 선언하며, 다음과 같은 형태로 작성됩니다:

class Client {
 private:
  int chunks_in_flight_ = 0;
};

이 기본 초기화 코드는 해당 클래스의 모든 생성자(심지어 C++가 자동으로 생성한 생성자 포함)로 전파됩니다.
이 방법은 데이터 멤버가 많은 클래스에 유용하며, 특히 bool, int, double, 원시 포인터 같은 타입에 적합합니다.
기본 타입의 비정적 데이터 멤버는 초기화를 빠뜨리기 쉽기 때문입니다.
사실 모든 타입의 비정적 데이터 멤버에 대해 초기화를 선언할 수 있습니다.

기본 멤버 초기화는 간단한 구조체 선언에도 유용합니다. 이러한 구조체에는 사용자 정의 생성자가 없습니다:

struct Options {
  bool use_loas = true;
  bool log_pii = false;
  int timeout_ms = 60 * 1000;
  std::array<int, 4> timeout_backoff_ms = { 10, 100, 1000, 10 * 1000 };
};

멤버 초기화 덮어쓰기

클래스 생성자가 이미 기본 초기화 값을 가진 데이터 멤버를 초기화하는 경우,
생성자의 초기화 값이 기본 초기화 값을 덮어씁니다:

class Frobber {
 public:
  Frobber() : ptr_(nullptr), length_(0) { }
  Frobber(const char* ptr, size_t length)
    : ptr_(ptr), length_(length) { }
  Frobber(const char* ptr) : ptr_(ptr) { }
 private:
  const char* ptr_;
  // length_에는 기본 초기화 값이 있음
  const size_t length_ = strlen(ptr_);
};

이 코드는 기존 방식으로 작성된 다음 코드와 동일합니다:

class Frobber {
 public:
  Frobber() : ptr_(nullptr), length_(0) { }
  Frobber(const char* ptr, size_t length)
    : ptr_(ptr), length_(length) { }
  Frobber(const char* ptr)
    : ptr_(ptr), length_(strlen(ptr_)) { }
 private:
  const char* ptr_;
  const size_t length_;
};

여기에서 첫 번째와 두 번째 Frobber 생성자는 비정적 변수에 대한 초기화를 명시적으로 제공합니다.
따라서 이 두 생성자는 length_의 기본 초기화 값을 사용하지 않습니다.
반면, 세 번째 Frobber 생성자는 length_의 초기화를 명시하지 않았으므로, 기본 초기화 값이 사용됩니다.

초기화 순서

C++에서 모든 비정적 변수는 선언된 순서대로 초기화됩니다.

첫 번째와 두 번째 Frobber 생성자는 length_에 대해 명시적으로 초기화 값을 제공하므로,
기본 멤버 초기화 값은 무시됩니다. 이 경우, 기본 초기화 값은 코드 생성에 기여하지 않습니다.

참고: 이전 문서에서는 기본 멤버 초기화를 NSDMI(Non-Static Data Member Initializer)로 언급하기도 합니다.


결론

기본 멤버 초기화는 프로그램을 더 빠르게 만들어주지는 않습니다.
그러나 새로운 생성자나 데이터 멤버를 추가할 때 실수로 초기화를 빠뜨리는 버그를 줄이는 데 도움을 줍니다.

주의사항: 정적 멤버 초기화와의 차이점

비정적 멤버 초기화와 정적 멤버 초기화를 혼동하지 않도록 주의하세요:

class Alpha {
 private:
  static int counter_ = 0;
};

위 코드는 정적 멤버 초기화로, counter_는 정적 변수입니다.
이는 비정적 멤버 초기화와 다르며, 정적 멤버 변수와 비정적 멤버 변수의 차이점과도 일맥상통합니다.