2024. 3. 24. 16:54ㆍProgramming 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 |