핵심 정리
class Player {
public:
// 멤버 초기화 리스트 — 본문 대입보다 효율적
Player(std::string name, int hp)
: name_{std::move(name)}, hp_{hp}, alive_{true} { }
// 위임 생성자
Player() : Player{"Unknown", 100} { }
private:
std::string name_;
int hp_;
bool alive_;
};문법
초기화 리스트가 본문 대입보다 효율적인 이유
생성자 본문에서 this->name = name처럼 대입하면 멤버가 먼저 기본 생성된 후 대입이 일어납니다 (두 단계). 초기화 리스트는 멤버를 직접 초기화하므로 한 단계입니다. std::string 같은 클래스 타입은 차이가 납니다.
class Bad {
std::string name_;
public:
Bad(std::string n) {
name_ = n; // 1) string 기본 생성(빈 문자열) 2) 복사 대입 — 두 단계
}
};
class Good {
std::string name_;
public:
Good(std::string n) : name_{std::move(n)} { } // 이동 생성 한 단계
};const·참조 멤버는 초기화 리스트 필수
const 멤버와 참조 멤버는 생성 시 딱 한 번만 초기화할 수 있습니다. 생성자 본문은 이미 초기화된 후이므로 여기서 값을 줄 수 없습니다.
class Config {
public:
Config(int version, std::string& log)
: version_{version}, // const — 리스트에서만 가능
log_{log} // 참조 — 리스트에서만 가능
{ }
private:
const int version_;
std::string& log_;
};위임 생성자 — 중복 제거
C++11 위임 생성자를 쓰면 한 생성자에서 다른 생성자를 호출해 초기화 로직을 한 곳에 모을 수 있습니다.
class Server {
public:
Server(std::string host, int port, int timeout)
: host_{std::move(host)}, port_{port}, timeout_{timeout} { }
Server(std::string host, int port)
: Server{std::move(host), port, 30} { } // 위임
Server(std::string host)
: Server{std::move(host), 8080} { } // 위임 체인
private:
std::string host_;
int port_, timeout_;
};클래스 내 기본값 초기화 — in-class initializer
C++11부터 멤버 선언 시 기본값을 직접 지정할 수 있습니다. 생성자에서 별도로 초기화하지 않으면 이 값이 쓰입니다.
class Widget {
int width_ = 100; // 기본값 100
int height_ = 50;
bool visible_ = true;
std::string label_ = "untitled";
public:
Widget() {} // width=100, height=50 사용
Widget(int w, int h) : width_{w}, height_{h} {} // label, visible은 기본값
Widget(std::string l) : label_{std::move(l)} {} // width, height는 기본값
};explicit — 암시적 변환 방지
단일 인수 생성자는 기본적으로 암시적 변환 생성자로 동작합니다. explicit을 붙이면 명시적 변환만 허용합니다.
class Radius {
public:
explicit Radius(double r) : r_{r} {} // explicit — 암시적 변환 차단
double r_;
};
void drawCircle(Radius r) { }
drawCircle(5.0); // ❌ 컴파일 오류 — 암시적 변환 불가
drawCircle(Radius{5.0}); // ✅ 명시적 생성자 호출
// explicit 없는 경우 (나쁜 예)
class BadRadius {
public:
BadRadius(double r) : r_{r} {} // 암시적 변환 허용
double r_;
};
drawCircle(5.0); // ✅ 컴파일되지만 의도와 다를 수 있음= default / = delete
= default는 컴파일러가 생성할 기본 구현을 명시적으로 사용합니다. = delete는 해당 함수를 삭제해 특정 사용을 컴파일 오류로 만듭니다.
class Singleton {
public:
Singleton(const Singleton&) = delete; // 복사 금지
Singleton& operator=(const Singleton&) = delete; // 복사 대입 금지
static Singleton& instance();
private:
Singleton() = default;
};체크포인트
| 상황 | 적합한 선택 |
|---|---|
| 클래스 타입 멤버 초기화 | 멤버 초기화 리스트 (본문 대입보다 효율) |
| const·참조 멤버 | 멤버 초기화 리스트 필수 |
| 생성자 코드 중복 제거 | 위임 생성자 (C++11) |
| 멤버 기본값 (생성자마다 공통) | 클래스 내 기본값 초기화 = value |
| 암시적 변환 방지 | 단일 인수 생성자에 explicit |
| 기본 생성자 복구 | Constructor() = default |
| 복사/이동 금지 | = delete |
주의할 점
초기화 리스트의 순서는 선언 순서를 따릅니다. 리스트 순서와 선언 순서가 다르면 의존 관계에서 정의되지 않은 값이 사용될 수 있습니다.
class Danger {
int a_;
int b_; // 선언 순서: a_, b_
public:
// ❌ 리스트 순서가 선언 순서와 반대
Danger(int x) : b_{x}, a_{b_ + 1} { }
// a_가 먼저 초기화(선언 순서) → b_는 아직 쓰레기값
};
// ✅ 선언 순서와 리스트 순서를 일치시킴
class Safe {
int a_;
int b_;
public:
Safe(int x) : a_{x + 1}, b_{x} { } // 선언 순서 = 리스트 순서
};참고 링크
1 sources