Even Idiots Can Make Game
생성자와 멤버 초기화 목록
생성자는 특수한 선언 문법과 함께 선언되는 비정적 멤버 함수로, 해당 클래스 형식의 객체를 초기화하는 데에 사용된다.
클래스-이름(파라미터-목록)
생성자는 이름이 없으며 직접 호출할 수 없다.
생성자는 초기화가 필요한 곳에서 호출되며, 초기화 규칙에 따라서 적절한 것이 선택된다.
explicit
키워드가 없는 생성자는 변환 생성자라고 부른다. constexpr
키워드가 없는 생성자는 형식을 리터럴 형식으로 만든다.
아무런 인자 없이 호출되는 생성자를 기본 생성자(Default Constructor)라고 부른다.
같은 형식의 다른 객체를 인자로 받는 생성자를 복사 생성자(Copy Constructor), 혹은 이동 생성자(Move Constructor)라고 부른다.
생성자 함수의 본문이 실행되기 전에:
- 모든 직접 기초 클래스들
- 가상 기초 클래스들
- 비정적 데이터 멤버 에 대한 초기화가 모두 완료된다.
멤버 초기화자 목록은 이러한 서브 객체들을 기본값이 아닌 값으로 초기화할 수 있는 곳이다. 멤버 초기화자의 문법은 다음과 같다.
클래스-혹은-식별자(표현식-목록) // (1)
클래스-혹은-식별자 중괄호-초기화-목록 // (2)
파라미터-팩... // (3)
- (1)의 경우
클래스-혹은-식별자
이름으로 지정된 기초 클래스나 멤버를 직접 초기화를 이용해 초기화하거나, 혹은표현식-목록
이 비어있는 경우 값 초기화를 이용해 초기화 한다. - (2)의 경우
클래스-혹은-식별자
이름으로 지정된 기초 클래스나 멤버를 목록 초기화를 통해 초기화한다.- 목록 초기화는 목록이 비어있는 경우 값 초기화이고, Aggregate를 초기화하는 경우 Aggregate 초기화이다.
- (3) 팩 확장을 통해 다수의 기초 클래스를 초기화한다.
struct S {
int n;
// [생성자 선언]
S(int);
// [생성자 정의]
// `: n(7)`은 초기화자 목록
// `: n(7) {}`은 함수 몸체
S(): n(7) {}
};
// [생성자 정의]
// `: n{x}` 는 초기화 목록
S::S(int x): n{x} {}
int main() {
S s1; // S::S()를 호출함
S s2(10); // S::S(int)를 호출함
}
다음의 경우 반드시 멤버 초기화자를 명시해야 한다:
- 기본 초기화가 불가능한 기초 클래스
- 기본 초기화가 불가능하거나, 멤버 초기화자로부터 초기화할 수 없는 비정적 데이터 멤버:
- 참조 타입 멤버
const
한정자가 붙은 멤버
가상 기본 클래스와 관련된 초기화자:
클래스-혹은-식별자
가 가상 기초 클래스를 이름으로 지칭하는 초기화자는, 현재 생성 중인 객체가 가장 파생된 클래스(Most Derived Class)가 아닌 경우 무시된다.1
초기화자 표현식 내의 이름 평가: 초기화자의 표현식 목록 또는 중괄호 초기화 목록 내에 나타나는 이름들은, 해당 생성자의 범위 내에서 평가된다. 2
#1 객체의 생성
유도 클래스의 생성자가 호출될 때, 기초 클래스의 기본 생성자가 자동으로 호출된다.
기초 클래스의 기본 생성자가 존재하지 않거나, 자동 생성할 수 없고, 혹은 접근할 수 없는 경우 컴파일 오류가 발생한다.
다음 코드가 왜 말이 안 되는 코드인지 짚어낼 수 있어야 한다.
class Super {
public:
int i_;
};
class Derived: public Super {
public:
Derived(const int& i):
i_(i)
{}
};
#2 멤버 초기화자 목록
생성자의 함수 본문 중괄호가 시작되기 이전에, 멤버 초기화자 목록이 올 수 있다.
C++의 멤버 초기화자 목록에서는 오직 자신의 클래스에 선언된 멤버 변수만 직접 초기화 할 수 있다. (물론 생성자 위임도 가능하다)
부모 클래스의 멤버에 대해서는 부모 클래스의 생성자에게 초기화를 맡겨야 하며, 직접 멤버를 초기화 할 수는 없다.
(초기화자 목록에 등장하는 이름들은 생성자의 스코프 내에서 평가된다.)
i_
는 Derived
의 멤버가 아니기 때문에 Derived
의 멤버 초기화자 목록에서 호출할 수 없다.
객체의 생성 과정은 다음과 같이 3단계임
- 메모리 공간 할당
- 이니셜라이저를 이용한 멤버 변수 초기화
- 생성자의 몸체부(
{}
) 실행
2번을 제외하면 1, 3은 필수적인 과정임. 특히, 3의 경우 생성자가 삽입되어 이루어지는 과정임
가상 함수 테이블은 생성자가 끝난 후에나 완벽히 설정되므로, 부모의 가상함수 테이블을 기반으로 작동함. 그러므로 생성자 내에서 가상 함수를 호출하는 것이 무슨 의미인지 알지 못한다면, 호출하지 않는 것이 좋음
2번은 명시하지 않으면 암시적으로 작성된다고 보면 됨
멤버 이니셜라이저에서 우선 기초 클래스의 생성자가 명시되었든 안 되었든 호출되려고 함. 호출이 어떤 이유에서든 불가능한 경우, 컴파일 오류가 발생함. 그리고 클래스의 각 멤버에 대해서, 클래스 타입의 경우 명시한 생성자가 호출되어 초기화되거나, 암시적으로 기본 생성자가 호출됨. (이 역시 암시적으로 호출되는 경우 기본 생성자에 접근이 불가능하면 컴파일 오류가 발생함) 기본 타입의 경우 명시한 값으로 초기화 되나, 명시하지 않은 경우 쓰레기값이 들어감.
C++11 부터 도입된, 클래스 멤버에 직접 초기화를 하는 것은 멤버 이니셜라이저와 동일하게 작동함. C++ 코어 가이드라인에는 클래스 멤버에 직접 초기화를 명시하는 것을 추천함
#1 참고 문헌
부모 클래스의 멤버에 대해서는 부모 클래스의 생성자에게 초기화를 맡겨야 하며, 직접 멤버를 초기화 할 수는 없다.