Replicated

[C++] 상속 시 생성자 탐구 본문

지식

[C++] 상속 시 생성자 탐구

라구넹 2025. 5. 27. 14:42

기본 원칙

1. 기본 클래스의 생성자가 먼저 호출됨

2. 그 후 멤버 객체들의 생성자가 호출

3. 마지막으로 파생 클래스의 생성자 본문이 실행

 

#include <iostream>
using namespace std;

class Base {
public:
    Base() { cout << "Base constructor\n"; }
};

class Derived : public Base {
public:
    Derived() { cout << "Derived constructor\n"; }
};

출력:

Base constructor
Derived constructor

 

 

생성자 초기화 리스트와 기본 클래스

class Base {
public:
    Base(int x) { cout << "Base(int)\n"; }
};

class Derived : public Base {
public:
    Derived(int x) : Base(x) {
        cout << "Derived(int)\n";
    }
};

파생 클래스 생성자는 기본 클래스 생성자를 초기화 리스트에서 명시적으로 호출 가능

* 기본 클래스에 기본 생성자 없으면 파생 클래스는 명시적으로 생성자 호출 필수

 

 

생성자의 상속

class Base {
public:
    Base(int x) { cout << "Base(int)\n"; }
};

class Derived : public Base {
public:
    using Base::Base; // Base 생성자 상속
};

Derived d(10); → Base(int) 호출됨.

* 기본 클래스의 복사, 이동 생성자는 자동으로 상속되지 않음

* Derived 자체 멤버는 여전히 초기화 필요

 

 

다중 상속에서의 생성자 호출

class A { public: A() { cout << "A\n"; } };
class B { public: B() { cout << "B\n"; } };
class C : public A, public B {
public:
    C() { cout << "C\n"; }
};

출력:

A

B

C

선언 순서대로 호출됨

 

 

가상 상속과 생성자

class A { public: A() { cout << "A\n"; } };
class B : virtual public A { public: B() { cout << "B\n"; } };
class C : virtual public A { public: C() { cout << "C\n"; } };
class D : public B, public C {
public:
    D() : A() { cout << "D\n"; }  // A를 여기서 호출해야 함
};

- A는 버츄얼로 상속되었으므로 한 번만 호출됨

출력:

A

B

C

D

 


* 가상 상속

- 다이아몬드 상속 문제를 해결하기 위해 도입한 개념 (여러 경로를 통해 동일한 기본 클래스가 중복 상속되는 것)

class A {
public:
    void hello() { std::cout << "Hello from A\n"; }
};

class B : public A {};
class C : public A {};
class D : public B, public C {};

이 경우 D 안에 A가 두 번 존재

D d;
d.hello(); // 컴파일 에러: 어떤 A의 hello인지 모름

D는 두 개의 A 인스턴스를 가짐 -> A::hello() 호출 시 모호성 문제가 생김

 

class A {
public:
    void hello() { std::cout << "Hello from A\n"; }
};

class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};

그래서 가상 상속 도입

A를 가상으로 상속하고, D는 A의 단일 인스턴스만 가지게 됨

* 이를 위해 vptr 및 오프셋을 이용해 객체 내부 구조를 다르게 구성함

 

class A {
public:
    A(int x) { std::cout << "A(" << x << ")\n"; }
};

class B : virtual public A {
public:
    B() : A(0) { std::cout << "B()\n"; }
};

class C : virtual public A {
public:
    C() : A(0) { std::cout << "C()\n"; }
};

class D : public B, public C {
public:
    D() : A(42) { std::cout << "D()\n"; }
};

단, 가상 기본 클래스의 생성자는 가장 하위 클래스에서 직접 호출해야 함

B, C는 구현해도 무시됨


생성자와 객체 슬라이싱

class Base {
public:
    Base() { cout << "Base()\n"; }
};

class Derived : public Base {
public:
    Derived() { cout << "Derived()\n"; }
};

Base b = Derived(); // slicing 발생, Derived 부분 잘림

* 객체 슬라이싱

- 파생 클래스 객체를 기본 클래스 타입으로 값 복사할 때 파생 클래스 고유의 멤버가 잘려나가고 기본 클래스 부분만 복사되는 현상

 

#include <iostream>
using namespace std;

class Base {
public:
    int a = 1;
    void show() { cout << "Base: a = " << a << endl; }
};

class Derived : public Base {
public:
    int b = 2;
    void show() {
        cout << "Derived: a = " << a << ", b = " << b << endl;
    }
};

int main() {
    Derived d;
    Base b = d;  // 객체 slicing 발생

    b.show();    // Base::show() 호출
    // b.b는 존재하지 않음. Derived의 b 잘림
}

Base는 b를 모름.

 실행 결과: a 부분만 나옴

 

피하려면?

1. 객체를 값으로 다루지 말고 포인터/참조로 다뤄야 함

2. 가상 함수 사용 시 항상 참조나 포인터 기반으로 접근

 

 

생성자와 템플릿 상속

template <typename T>
class Base {
public:
    Base(T val) { cout << "Base(" << val << ")\n"; }
};

class Derived : public Base<int> {
public:
    using Base<int>::Base;
};

템플릿 기반 상속에서는 생성자가 자동으로 상속되지 않음

애초에 C++에서 파생 클래스는 기본 클래스의 생성자를 호출 가능한 구조임

생성자 상속은 using.. 수동 상속임 (C++ 11 이상)

 

class Base {
public:
    Base(int x) { std::cout << "Base(" << x << ")\n"; }
};

class Derived : public Base {
public:
    Derived(int x) : Base(x) {}  // ← 직접 호출
};

이건 호출임

 

class Derived : public Base {
public:
    using Base::Base;  // ✅ Base의 생성자들을 Derived에 "상속"
};

이게 상속

 

 

explicit 생성자와 상속

class Base {
public:
    explicit Base(int x) { cout << "Base(" << x << ")\n"; }
};

class Derived : public Base {
public:
    Derived(int x) : Base(x) {}
};

기본 클래스 생성자가 explicit인 경우 파생 클래스에서 명시적으로 전달해야 함

 

explicit 생성자?

암시적 변환을 막는 키워드

- 호출 생략해도 기본 생성자는 암시적으로 호출되는데, explicit 선언하면 명시적으로 호출해야 한다는 것

- 인자 필요한 생성자도 자동 호출 안됨

 

 

A <- B <- C 상속 구조에서 C가 B나 A의 생성자를 명시적 호출 시 순서는 어떻게 바뀌는가?

=> 생성자 실행 순서는 항상 기본 클래스 -> 중간 클래스 -> 파생 클래스

명시적으로 호출해봐야 실행 순서 안바뀜

 

 

소멸자 호출 순서?

class A {
public:
    A()  { std::cout << "A constructor\n"; }
    virtual ~A() { std::cout << "A destructor\n"; }
};

class B : public A {
public:
    B()  { std::cout << "B constructor\n"; }
    ~B() { std::cout << "B destructor\n"; }
};

class C : public B {
public:
    C()  { std::cout << "C constructor\n"; }
    ~C() { std::cout << "C destructor\n"; }
};

 

int main() {
    C obj;
}

 

결과..

A constructor
B constructor
C constructor
C destructor
B destructor
A destructor

 

안전하게 생성, 제거하기 위해 이 순서는 고정임

'지식' 카테고리의 다른 글

[Math] 외적  (0) 2025.05.28
[Math] 내적  (0) 2025.05.28
[C++] virtual 탐구  (0) 2025.05.27
[C++] L Value & R Value  (0) 2025.05.27
[DS] C++ Deque 랜덤 액세스 O(1)?  (0) 2025.03.21