: 뒤에 붙는 멤버 이니셜라이저 리스트(초기화 리스트)의 의미const 멤버 / 참조 멤버가 왜 초기화 리스트를 강제하는지const/참조 멤버가 왜 본문에서 대입이 불가능한지 설명할 수 있다.생성자에는 크게 두 구간이 있습니다.
: 뒤에 오는 부분 { ... } class Circle {
public:
Circle() : _color(1) // 여기서 _color가 먼저 초기화됨
{
// 여기서는 이미 _color가 1인 상태
}
private:
int _color;
};
규칙은 3개로 끝납니다.
┌─────────────────────────────────────────────────────────────────────────────┐
│ Knight k1; (Knight : public Player, 멤버: Inventory _inventory) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ [생성 순서] │
│ ① Player() ← 기반(부모) 먼저 │
│ │ │
│ ② Inventory() ← 멤버(선언 순서대로) │
│ │ │
│ ③ Knight() ← 자식 생성자 본문 │
│ │
│ [소멸 순서] (생성의 역순) │
│ ① ~Knight() ← 자식 먼저 │
│ │ │
│ ② ~Inventory() ← 멤버 │
│ │ │
│ ③ ~Player() ← 기반(부모) 마지막 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
자주 하는 착각:
초기화 리스트는 “보기 좋은 순서”로 적을 수 있지만, 실제 초기화는 멤버 선언 순서대로 진행됩니다.
class Foo {
public:
Foo() : _b(2), _a(1) {} // 이렇게 적어도...
private:
int _a; // _a가 먼저 초기화됨
int _b; // 그 다음 _b
};
컴파일러가 경고를 주는 경우도 있으니(예: reorder 관련 경고), “선언 순서 = 초기화 순서”를 기준으로 코드를 읽는 습관을 들이는 게 안전합니다.
핵심 차이:
#include <iostream>
class Inventory {
public:
Inventory() { std::cout << "Inventory()\n"; }
explicit Inventory(int gold) : _gold(gold) { std::cout << "Inventory(int)\n"; }
private:
int _gold = 0;
};
class KnightA {
public:
KnightA() // 비추천: 본문에서 대입
{
_inventory = Inventory(100);
}
private:
Inventory _inventory;
};
class KnightB {
public:
KnightB() : _inventory(100) // 추천: 초기화 리스트
{
}
private:
Inventory _inventory;
};
예상되는 출력 감각:
KnightA:Inventory() (기본 생성) → Inventory(int) (임시 생성) → 대입KnightB:Inventory(int) 한 번만 생성추가로 중요한 포인트:
const 멤버를 가진 타입, 참조 멤버를 가진 타입 등)이라면, 본문에서 _inventory = ... 자체가 컴파일 불가가 될 수 있습니다.┌─────────────────────────────────────────────────────────────────────────────┐
│ 비효율 (본문에서 대입) 효율 (초기화 리스트) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ KnightA() { KnightB() │
│ _inventory = Inventory(100); // ① : _inventory(100) // ① │
│ } // 한 번만 생성 │
│ { } │
│ │
│ ① Inventory() 기본 생성 │
│ ② Inventory(100) 임시 객체 생성 │
│ ③ 대입으로 덮어쓰기 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
T&): 생성 시점에 “어디를 참조할지”가 확정되어야 함const T): 생성 시점에 값이 확정되어야 함그래서 둘 다 초기화 리스트에서만 초기화 가능하고, 생성자 본문에서 대입하면 컴파일 에러가 납니다.
class Circle {
public:
Circle() : _color(1), _colorRef(_color), _colorConst(_color) {}
private:
int _color;
int& _colorRef;
const int _colorConst;
};
주의:
#include <iostream>
class Circle {
public:
Circle() : Circle(3) // 다른 생성자에게 초기화 책임을 위임
{
std::cout << "Circle()\n";
}
explicit Circle(int a)
{
std::cout << "Circle(" << a << ")\n";
}
};
생성자 위임을 쓰는 이유:
const 멤버/참조 멤버가 생성자 본문에서 대입이 안 되는 이유는?