HOWTO · C++
C++의 빈 생성자
C++에서 본문이 비어 있는 생성자와 기본 생성자의 차이, 그리고 멤버를 안전하게 초기화하는 방법을 알아봅니다.
이 페이지의 내용
“빈 생성자”는 보통 사용자가 직접 작성한, 본문이 비어 있는 생성자를 뜻합니다. 이는 흔히 쓰이는 표현일 뿐, C++ 언어의 별도 생성자 범주는 아닙니다. 핵심은 클래스에 인자 없이 호출할 수 있는 기본 생성자가 있는지입니다. 이 생성자는 기본 인수가 지정된 매개변수를 가질 수도 있으므로, “기본 생성자”라고 해서 매개변수 목록이 반드시 비어 있어야 하는 것은 아닙니다.
본문이 비어 있는 생성자가 모든 멤버에 유효한 값을 자동으로 넣어 주는 것은 아닙니다. 멤버 초기화 규칙은 객체를 만드는 방식과 클래스의 선언에 따라 달라집니다. 클래스가 안정적인 초기 상태를 가져야 한다면 클래스 내 멤버 초기화자나 멤버 이니셜라이저 목록을 사용하세요. (값 초기화; 멤버 이니셜라이저 목록)
빈 생성자와 기본 생성자
기본 생성자는 인자 없이 호출할 수 있는 생성자입니다. 예를 들어 Gauge(int level = 1)은 인자를 생략해 호출할 수 있으므로 기본 생성자입니다. Gauge() {}처럼 본문이 비어 있는 생성자도 기본 생성자에 해당합니다. 다만 생성자 본문이나 멤버 이니셜라이저 목록에서 유용한 초기화 작업을 수행할 수도 있습니다.
C++에서는 클래스에 사용자가 선언한 생성자가 없을 때 기본 생성자를 암시적으로 선언할 수 있습니다. 다른 생성자를 선언하면 컴파일러가 인자 없는 생성자까지 암시적으로 제공하지는 않습니다. 인자 없이도 객체를 생성해야 한다면 해당 오버로드를 직접 선언하거나, 적절한 경우 = default로 컴파일러가 생성하는 형식을 요청하세요. 기본 지정 생성자와 본문이 비어 있는 생성자는 서로 다른 선언입니다. 전자는 컴파일러에 해당 연산을 생성하도록 요청하고, 후자는 본문에서 아무 작업도 하지 않는 사용자가 작성한 함수입니다.
관련 기본 생성자 안내에서는 이 생성자 범주에 관한 더 폭넓은 규칙을 다룹니다. 여기서는 빈 본문의 의미와 객체의 멤버에 미치는 영향을 살펴봅니다.
빈 생성자 정의하기
인자 없는 생성자는 클래스와 이름이 같고 반환형이 없습니다. 아래 프로그램에서는 value에 클래스 내 초기값을 지정합니다. meter를 만들면 생성자가 호출되고, 마지막 줄은 멤버 값을 표준 출력에 씁니다. 클래스 내 초기화자는 객체를 생성하는 과정에서 생성자 본문보다 먼저 적용됩니다. 이 완전한 프로그램은 C++ 표준 기능만 사용하므로 단독으로 컴파일할 수 있습니다. 실행 결과는 아래와 같습니다.
이 예제를 실행해 보세요.
이 섹션의 코드와 표시되는 레이블 또는 출력은 영어로 유지됩니다.
#include <iostream>
struct Meter {
int value{7};
Meter() {}
};
int main() {
Meter meter;
std::cout << meter.value << '\n';
}
7
Meter() {} 선언의 본문은 비어 있고, value{7}이 본문이 실행되기 전에 멤버를 초기화합니다. meter를 만들면 인자 없는 생성자가 호출되고 프로그램은 멤버 값을 출력합니다. 이 예제는 생성자의 역할과 멤버가 초기화되는 경로를 구분해 보여 줍니다.
멤버 이니셜라이저 목록으로도 멤버를 초기화할 수 있습니다. 예를 들면 Meter() : value(7) {}처럼 작성합니다. 참조 멤버나 const 데이터 멤버처럼 생성 후 대입할 수 없는 멤버에는 이 방식이 필요합니다. 불변 조건을 설정할 때도 멤버 이니셜라이저 목록이 가장 명확한 방법인 경우가 많습니다. 목록에서 이름을 적은 순서와 관계없이 멤버는 클래스에 선언된 순서대로 초기화됩니다.
초기화자가 없는 멤버에는 어떤 일이 생길까
빈 생성자 본문은 스칼라 멤버를 초기화하지 않습니다. int count; 같은 스칼라에 클래스 내 초기화자가 없고 멤버 이니셜라이저 목록에도 지정되지 않았다면, 객체를 기본 초기화할 때 해당 멤버는 초기화된 값을 갖지 않습니다. 값을 대입하기 전에 읽는 것은 안전하지 않습니다. 설정 함수가 호출되기 전에 읽히는 일을 클래스가 모두 막아 주지 않는다면, 나중에 설정 함수가 호출될 것이라고 기대하지 마세요.
생성자에서 초기화를 생략한 멤버에는 기본 멤버 초기화자가 있으면 그 초기화자가 적용됩니다. 기본 멤버 초기화자가 없으면 해당 형식에 따른 규칙으로 초기화됩니다. 클래스형 멤버는 기본 생성자를 선택해 기본 초기화됩니다. 그렇다고 모든 멤버에 프로그램에서 필요한 값이 들어가는 것은 아닙니다. 빈 본문만으로 객체가 완전히 초기화된다고 가정하지 말고, 각 멤버의 형식과 초기화 요건을 확인하세요.
기본 초기화와 값 초기화는 서로 다른 초기화 방식입니다. 클래스 객체를 기본 초기화하면 선택된 기본 생성자가 호출됩니다. 선택된 기본 생성자가 사용자 제공 생성자가 아니라면, 값 초기화는 먼저 객체를 0으로 초기화한 다음 기본 초기화를 수행할 수 있습니다. {} 본문을 가진 사용자가 작성한 생성자는 사용자 제공 생성자이므로, 해당 클래스 객체를 값 초기화해도 다른 이유로 초기화되지 않는 스칼라 멤버가 안전해지지는 않습니다. 이 규칙은 혼동하기 쉬우므로 의도한 상태를 표현할 때는 클래스 내 초기화자나 멤버 이니셜라이저 목록을 사용하는 편이 더 확실합니다.
예를 들어 struct Sample { int count{}; Sample() {} };에서는 클래스 내 초기화자가 count를 0으로 초기화합니다. 반면 struct Sample { int count; Sample() {} };에서는 생성자가 있다는 이유만으로 count가 초기화되지 않습니다. 빈 함수 본문의 중괄호는 멤버 초기화자가 아닙니다.
빈 생성자가 유용한 경우
객체를 만들 때 클래스가 별도로 수행할 작업이 없고 모든 멤버에 적절한 초기화 규칙이 이미 있다면 본문이 비어 있는 생성자도 적절합니다. 기본 생성이 필요한 프레임워크나 컨테이너에서 사용할 수 있도록 의도적으로 공개 생성 연산을 제공할 때도 쓸 수 있습니다. 어느 경우든 호출자가 설정 함수를 정해진 순서로 호출해야만 유효해지는 객체가 아니라, 생성 직후부터 유효한 객체여야 합니다.
여러 생성자가 공유하는 단순한 기본값에는 클래스 내 멤버 초기화자를 우선 사용하세요. 초기값이 생성자 인자에 따라 달라지거나, const 또는 참조 멤버를 초기화하거나, 멤버 형식에 특정 생성자가 필요할 때는 멤버 이니셜라이저 목록을 사용하세요. 초기화 과정에 검증, 리소스 획득 또는 클래스 불변 조건 설정이 필요하다면 본문을 비워 두지 말고 적절한 생성자에서 처리하세요.
사용하는 연산에 따라 컬렉션과 배열의 원소가 기본 생성 가능해야 할 수 있습니다. 인자 없는 생성자를 명시적으로 선언하면 그 요건을 충족할 수 있지만, 그것만으로 초기화를 미루는 방식이 안전해지는 것은 아닙니다. 팩터리, 생성자 인자 또는 원소를 제자리에서 생성하는 컨테이너 연산이 필요한 상태를 더 분명하게 나타내는지 살펴보세요.
흔한 실수와 더 안전한 대안
“본문이 비어 있다”는 것을 “모든 멤버가 기본값을 받는다”와 같다고 여기지 마세요. 초기화자가 없는 스칼라는 기본 초기화 시 값이 정해지지 않은 상태로 남을 수 있습니다. 클래스 내 초기화자를 지정하거나, 멤버 이니셜라이저 목록에서 초기화하거나, 생성자 매개변수로 값을 전달받으세요.
매개변수가 있는 생성자를 선언해도 인자 없는 생성자가 계속 제공된다고 가정하지 마세요. 두 형식이 모두 필요하면 둘 다 선언하거나, 생성된 동작이 적절한 경우 인자 없는 형식을 명시적으로 기본 지정하세요. 생성자를 사용할 수 있는지는 클래스에서 선언한 순서가 아니라 선언 내용과 언어 규칙으로 결정됩니다.
마지막으로 나중에 설정 함수로 값을 채우려고 불완전한 상태의 객체를 만들지 마세요. 클래스에 필수 데이터가 있다면 생성자에서 전달받아 그곳에서 불변 조건을 설정하세요. 본문이 의도적으로 아무 작업도 하지 않고 멤버 선언 자체가 유효한 초기 상태를 제공할 때만 본문이 비어 있는 생성자를 두세요.