이번 글에서는 C++ 클래스의 내부 동작(Class Internals)에 대해 살펴보겠습니다. 그에 앞서 내부 동작과 밀접한 관련이 있는 기본 생성자(Default Constructor)부터 먼저 이해할 필요가 있습니다.
기본 생성자란 무엇인가?
기본 생성자란 어떠한 인수도 받지 않는 생성자를 의미합니다. 사용자가 직접 정의할 수도 있고, 컴파일러가 암묵적으로 만들어줄 수도 있습니다.
그렇다면 왜 기본 생성자가 필요할까요? 핵심은 다음과 같습니다.
- 사용자가 기본 생성자를 정의하지 않으면, 컴파일러가 암묵적으로 기본 생성자를 선언합니다.
- 기본 생성자는 클래스의 내부 구조(internals)를 초기화하는 데 사용됩니다.
- 단, 기본 생성자는 클래스의 일반 데이터 멤버(예:
int변수)까지 자동으로 초기화해주지는 않습니다.
컴파일러가 기본 생성자 코드를 삽입하는 경우
컴파일러는 특정 상황에서 기본 생성자를 호출하는 코드를 자동으로 삽입합니다. 대표적인 경우는 다음과 같습니다.
- 상속 관계: 어떤 클래스가 기본 생성자를 가진 부모 클래스로부터 파생된 경우, 컴파일러는 부모 클래스의 기본 생성자를 호출하는 코드를 삽입합니다.
- 멤버 객체 포함: 한 클래스 내부에 기본 생성자를 가진 다른 클래스의 객체가 멤버로 포함된 경우, 해당 객체의 기본 생성자를 호출하는 코드를 삽입합니다.
즉, 현재 객체가 생성되기 전에 기반 클래스(base class) 또는 내부에 포함된 객체의 생성자가 반드시 먼저 호출되도록 컴파일러가 코드를 추가해주는 것입니다.
예제 코드
다음 예제를 통해 이 개념을 명확히 이해해 보겠습니다.
#include<iostream>
using namespace std;
class Base {
public:
// 컴파일러가 "Base" 클래스를 위한 생성자를 자동으로 생성
};
class ClassA {
public:
ClassA() {
cout << "ClassA Constructor defined by user" << endl;
}
int x; // 자동으로 초기화되지 않음
};
class ClassB : public ClassA {
// 컴파일러가 ClassB 생성자를 만들고,
// ClassA 생성자를 호출하는 코드를 추가함
};
class ClassC : public ClassA {
public:
ClassC() { // 사용자 정의 생성자이지만,
// 컴파일러가 ClassA 생성자를 호출하는 코드를 추가함
cout << "User defined ClassC Constructor" << endl;
}
};
class ClassD {
public:
ClassD() {
// 사용자 정의 기본 생성자.
// 컴파일러가 ClassA 객체를 초기화하는 코드를 추가함
cout << "User-defined consturctor for ClassD Constructor" << endl;
}
private:
ClassA obj;
};
int main() {
Base baseObj;
ClassB b;
ClassC c;
ClassD d;
}실행 결과
ClassA Constructor defined by user ClassA Constructor defined by user User defined ClassC Constructor ClassA Constructor defined by user User-defined consturctor for ClassD Constructor
결과 분석
출력 결과를 보면 흥미로운 사실을 확인할 수 있습니다.
Base baseObj;— 아무 출력이 없습니다. 컴파일러가 만든 기본 생성자는 아무 작업도 하지 않기 때문입니다.ClassB b;— 사용자가 생성자를 정의하지 않았지만, 컴파일러가 삽입한 코드에 의해 부모 클래스인ClassA의 생성자가 호출되어 첫 번째 메시지가 출력됩니다.ClassC c;— 사용자가 정의한 생성자가 실행되기 전에 부모 클래스ClassA의 생성자가 먼저 호출됩니다. 따라서 "ClassA Constructor" 메시지가 "User defined ClassC Constructor"보다 먼저 나옵니다.ClassD d;— 마찬가지로 멤버 객체obj의 생성자가 먼저 호출된 후,ClassD자신의 생성자 본문이 실행됩니다.
이처럼 C++에서는 객체 생성 시 기반 클래스와 멤버 객체의 생성자가 항상 먼저 호출되며, 이를 보장하기 위해 컴파일러가 필요한 경우 기본 생성자를 암묵적으로 선언하고 호출 코드를 삽입합니다. 단, int x처럼 기본 타입의 데이터 멤버는 이 과정에서 초기화되지 않으므로 주의해야 합니다.