[C++] 상속(Inheritance)

2024. 3. 24. 16:54Programming Language/C++

상속(Inheritance)

• 무조건 부모 클래스의 생성자가 먼저 호출된다.

CChild()
    : CParent()  // 생략되어있다.
    , m_f(0.f)
{
    m_i = 0;.
    cout << "자식 생성자" << endl;
}

`CChild` 클래스의 생성자에 `CParent()`가 생략되어 있다.

무조건 부모 클래스의 생성자가 먼저 호출되기 때문에 `CParent()`를 생략하든, `m_f(0.f)`와 순서를 바꾸든 상관없이 `CParent()` 쪽이 먼저 초기화된다.

▷ 예시

#include <iostream>

using std::cout;
using std::endl;

void FuncA()
{
	cout << "Function A" << endl;
}

void FuncB()
{
	FuncA();
	cout << "Function B" << endl;
}

int main()
{
	FuncB();
	// 코드 컴파일은 순차적으로 되기 때문에 FuncA()가 먼저 완료가 되어있는 상태이다.
	// 때문에 FuncB()를 호출했을 때 실행 순서는 FuncA() → FuncB()이다.

	return 0;
}

▽ 출력 결과

Function A
Function B

상속에서 생성자의 동작 방식

• 자식 또는 부모 클래스는 상속 관계에서 다른 클래스의 멤버를 초기화할 수 없다. (값 대입이 아닌 이니셜라이저(초기화) 한정)

  ➜ 각자 자기 할 일은 자기가 하자.

클래스의 객체가 만들어질 때 생성자가 호출되는데, 이때 자식 클래스의 이니셜라이저에 부모 클래스의 멤버를 넣을 수 없다.

각자 파트에서 구현되어 있는 생성자로 각자 초기화해야 한다.

때문에 자식 파트에서 부모 파트의 멤버를 초기화하려고 하면 오류가 발생한다.

CChild()
    : m_f(0.f)
    , m_i(0)  // 오류 => 각자 파트의 생성자가 초기화해줘야 한다.
        // 즉, m_i는 CParent가 초기화하게끔 맡겼어야 한다.
{
    m_i = 0;  // 값 대입은 가능
    cout << "자식 생성자" << endl;
}

 

생성자 호출 순서 : 자식 → 부모

생성자 실행 순서 (생성자 초기화 순서) : 부모 → 자식

 소멸자 호출 및 실행 순서 : 자식 → 부모

생성자는 자식부터 호출되고 부모로 올라간 다음, 부모부터 실행하고 자식을 실행하는 반면,

소멸자는 자식부터 호출되고 자식부터 실행한 다음, 부모 호출 및 실행하고 또 그 부모의 부모를 호출 및 실행한다.

생성자 실행 순서

`CChild`(자식 클래스)가 호출되면 실제 주체가 되는 객체는 `CChild`이므로 `CChild`의 생성자가 먼저 호출된다.

그다음에 부모 클래스의 생성자를 통해서 초기화하라고 호출한다.

즉, 자식 클래스의 생성자를 호출한 다음, 부모 클래스의 생성자를 호출해서 부모 클래스 멤버를 초기화한다.

 

메모리가 생기는 순서: 부모 → 자식

(이는 다형성에서 중요한 부분이다.)

`CParent` → `CChild` → `CChildChild`

▷ 예시

#include <iostream>

using std::cout;
using std::endl;

class CParent
{
protected:  // 아예 공개는 하지 않고 자식에서 접근하는 것만 허용
	int m_i;

public:
	void SetInt(int _a)
	{
		m_i = _a;
	}

	// 출력 함수
	void Output()
	{
		cout << "Parent" << endl;
	}

public:
	CParent()
		:m_i(0)
	{
		cout << "부모 생성자" << endl;
	}
	// 자식 쪽에서 딱히 명시해주지 않으면 기본 생성자가 호출된다.

	// 생성자 오버로딩
	CParent(int _a)
		: m_i(_a)
	{
		cout << "부모 생성자" << endl;
	}

	~CParent()
	{
		cout << "부모 소멸자" << endl;
		cout << "==========" << endl;
	}
};

// 클래스 CChild가 클래스 CParent를 상속받는다.
class CChild : public CParent
{
private:
	float m_f;

public:
	// 부모 파트의 입장에서 SetFloat()은 외부이다.
	void SetFloat(float _f)
	{
		m_f = _f;
		m_i = 100;
	}

public:
	CChild()
		: m_f(0.f)
	{
		m_i = 0;  // protected로 자식 쪽에서 접근하는 것은 허용했기 때문에 문제가 발생하지 않는다.
		cout << "자식 생성자" << endl;
	}

	~CChild()
	{
		cout << "자식 소멸자" << endl;
		cout << "==========" << endl;
        // 상속받은 부모의 소멸자를 호출하는 코드가 생략되어있다.
	}   
};

//자식의 자식 클래스
class CChildChild : public CChild
{
private:
	long long m_ll;
};

int main()
{
	CParent parent;  // 부모 생성자
	cout << "==========" << endl;
	CChild child;  // 부모 생성자
				   // 자식 생성자
	cout << "==========" << endl;
	parent.SetInt(10);
	//parent.m_i = 10;  // 오류 => protected이기 때문에 직접 접근할 수는 없다.

	child.SetInt(10);  // CParent 파트의 m_i에 10을 넣는다.
	child.Output();  // Parent  // 자식(CChild) 쪽에 구현된 Output()이 호출된다.
	cout << "==========" << endl;

	return 0;
    // 자식 소멸자
	// 부모 소멸자
    // 부모 소멸자
}

▽ 출력 결과

부모 생성자
==========
부모 생성자
자식 생성자
==========
Parent
==========
자식 소멸자
==========
부모 소멸자
==========
부모 소멸자
==========

`CChild child;`에서 `CChild` 객체가 생성될 때, `CParent()`의 기본 생성자가 먼저 호출되어 `m_i`를 초기화한다.

`CChild` 객체 소멸: 객체 `child`가 소멸될 때, 먼저 `CChild`의 소멸자가 호출되고, 그다음 `CParent`의 소멸자가 호출된다.

위 코드에서 부모 소멸자가 2번 호출된 이유
1. `parent` 객체 소멸 시 호출
2. `child` 객체 내부의 부모 클래스 부분이 소멸될 때 호출

'Programming Language > C++' 카테고리의 다른 글

[C++] 주석(Comment), 세미콜론(;)  (0) 2024.03.25
[C++] 문자  (0) 2024.03.24
[C++] 트리(Tree), set과 map  (0) 2024.03.22
[C++] list iterator, inline  (0) 2024.03.22
[C++] erase() (+ clear() 문제점)  (0) 2024.03.21