C++ 상속에서 생성자 호출 규칙
기본 클래스 생성자 우선 호출:
파생 클래스 객체를 생성하면 먼저 기본 클래스 생성자가 호출됩니다. 즉, 파생 클래스의 생성자 코드가 실행되기 전에 기본 클래스의 생성자가 실행되어 기본 클래스 멤버 변수를 초기화합니다.
명시적 호출 vs. 암묵적 호출:
- 명시적 호출: 파생 클래스 생성자에서
base_class_constructor(arg_list)
와 같은 형식으로 기본 클래스 생성자를 명시적으로 호출할 수 있습니다. 이를 통해 기본 클래스의 특정 생성자를 선택하여 사용할 수 있습니다. - 암묵적 호출: 명시적 호출이 없으면 파생 클래스 생성자는 기본 클래스의 기본 생성자를 암묵적으로 호출합니다. 기본 클래스에 기본 생성자가 없으면 컴파일 에러가 발생합니다.
생성자 초기화 목록 사용:
파생 클래스 생성자에서 : base_class(arg_list)
형식의 생성자 초기화 목록을 사용하여 기본 클래스 생성자에 인수를 전달할 수 있습니다. 이는 명시적 호출과 동일한 효과를 가지며, 코드를 보다 간결하게 작성할 수 있도록 합니다.
다중 상속:
다중 상속에서 여러 기본 클래스가 있는 경우, 생성자 호출 순서는 상속 선언 순서에 따라 결정됩니다. 즉, 먼저 선언된 기본 클래스의 생성자부터 차례대로 호출됩니다.
예제:
class Base {
public:
Base() { std::cout << "기본 클래스 생성자" << std::endl; }
Base(int x) { std::cout << "기본 클래스 생성자(int)" << std::endl; }
};
class Derived : public Base {
public:
Derived() : Base(10) { std::cout << "파생 클래스 생성자" << std::endl; }
};
int main() {
Derived d;
}
위 코드에서 출력 결과는 다음과 같습니다.
기본 클래스 생성자(int)
파생 클래스 생성자
C++ 상속 생성자 호출 예제 코드
Base 클래스:
class Base {
public:
Base() { std::cout << "기본 클래스 생성자 호출" << std::endl; }
Base(int x) { std::cout << "기본 클래스 생성자(int) 호출, x = " << x << std::endl; }
void info() {
std::cout << "기본 클래스 정보" << std::endl;
}
};
Derived 클래스:
class Derived : public Base {
public:
Derived() : Base(100) { std::cout << "파생 클래스 생성자 호출" << std::endl; }
Derived(int x, int y) : Base(x), member_var(y) { std::cout << "파생 클래스 생성자(int, int) 호출, y = " << y << std::endl; }
int member_var;
void info() override {
std::cout << "파생 클래스 정보" << std::endl;
}
};
메인 함수:
int main() {
// 기본 클래스 생성자 호출
Base b;
b.info();
// 파생 클래스 기본 생성자 호출
Derived d;
d.info();
// 파생 클래스 생성자(int, int) 호출
Derived d2(20, 30);
d2.info();
return 0;
}
출력 결과:
기본 클래스 생성자 호출
기본 클래스 정보
파생 클래스 생성자 호출
파생 클래스 정보
파생 클래스 생성자(int, int) 호출, y = 30
파생 클래스 정보
설명:
Base
클래스는 기본 생성자(Base()
)와 인수를 받는 생성자(Base(int x)
)를 가지고 있습니다.Derived
클래스는Base
클래스를 상속받고, 기본 생성자(Derived()
)와 두 개의 인수를 받는 생성자(Derived(int x, int y)
)를 가지고 있습니다.Derived
클래스의 기본 생성자는Base(100)
을 사용하여 기본 클래스 생성자를 명시적으로 호출합니다.Derived(int x, int y)
생성자는 생성자 초기화 목록을 사용하여Base(x)
를 사용하여 기본 클래스 생성자를 호출하고,member_var
멤버 변수를 초기화합니다.main
함수에서 각 생성자를 호출하고, 각 객체의info()
메서드를 호출합니다.
C++ 상속에서 생성자 호출 대체 방법
가상 기본 클래스 사용:
- 가상 기본 클래스를 사용하면 파생 클래스에서 기본 클래스 생성자 호출을 제어할 수 있습니다.
- 가상 기본 클래스는
virtual
키워드를 사용하여 기본 클래스를 선언합니다. - 파생 클래스에서 가상 기본 클래스의 생성자를 호출하려면
this->Base::Base(...)
와 같은 형식으로 명시적으로 호출해야 합니다.
class Base {
public:
virtual ~Base() = 0; // 가상 소멸자 선언
};
class Derived : public Base {
public:
Derived() { }
~Derived() override { } // 가상 소멸자 재정의
void callBaseConstructor() {
this->Base::Base(); // 명시적 호출
}
};
- 추상 기본 클래스를 사용하면 기본 클래스에서 생성자를 직접 정의할 수 없으므로 파생 클래스에서 반드시 생성자를 정의해야 합니다.
- 파생 클래스는 추상 기본 클래스의 생성자를 호출해야 하며, 명시적 또는 암묵적 호출이 가능합니다.
class Base {
public:
virtual void info() = 0; // 추상 메서드 선언
abstract:
};
class Derived : public Base {
public:
Derived() { }
void info() override {
std::cout << "파생 클래스 정보" << std::endl;
}
};
템플릿 메타프로그래밍 사용:
- 고급적인 방법으로는 템플릿 메타프로그래밍을 사용하여 생성자 호출을 제어할 수 있습니다.
- 템플릿 메타프로그래밍은 컴파일 타임에 코드를 생성하는 기술입니다.
- 이 방법은 복잡하고 숙련된 프로그래머에게만 적합하며, 일반적인 상황에서는 rarely 사용됩니다.
주의 사항:
- 기본 클래스 생성자 호출을 대체할 때는 주의해야 합니다. 부주의하게 대체하면 객체가 제대로 초기화되지 않아 오류가 발생할 수 있습니다.
- 가상 기본 클래스 또는 추상 기본 클래스를 사용하는 경우, 파생 클래스에서 반드시 가상 메서드와 추상 메서드를 재정의해야 합니다.
- 템플릿 메타프로그래밍은 복잡한 기술이며, 잘못 사용하면 코드를 이해하기 어렵게 만들 수 있습니다.
c++ inheritance constructor