TIL 2024/8/22

Sung Joo Lee·2024년 8월 22일

상속 part 3

이전 포스트에서 이어진다.
<>

상속에서의 생성자와 소멸자


상속에서의 생성자

  • 유도 클래스는 기본 클래스의 멤버를 포함하므로, 유도 클래스가 초기화 되기 이전에 기본 클래스에서 상속된 부분이 반드시 초기화 되어야 함

  • 유도 클래스 객체가 생성될 때

    • 먼저 기본 클래스의 생성자가 호출되고
    • 그 이후 유도 클래스의 생성자가 호출됨
class Base
{
public:
	Base() {
		std::cout << "Base 생성자 생성 " << std::endl;
	}

};

class Derived : public Base
{
public:
	Derived() {
		std::cout << "Derived 생성자 생성" << std::endl;
	}
};

int main()
{
	Derived d;
}

위의 코드를 실행 해보면 Base class의 생성자가 호출이 된다.

상속에서의 소멸자


  • 소멸자는 생성자와 반대 순서로 호출됨

  • 즉, 유도 클래스가 소멸될 때,

    • 먼저 유도 클래스의 소멸자가 호출되고,
    • 그 이후 기본 클래스의 소멸자가 호출됨

생성자와 소멸자의 상속


  • 유도 클래스는 기본 클래스의 생성자, 소멸자 및 오버로딩된 대입 연산자상속하지 않음

    • 멤버 함수들은 상속하는 것이 맞다.
  • 대신 , 기본 클래스의 생성자, 소멸자 및 오버로딩된 대입 연산자를 유도 클래스로부터 호출이 가능함

    • 나중에 자세하게 설명해줌

주의)

class Base
{
private:
	int value;
public:

	Base()
		:value{0} 
	{
		std::cout << "Base 인자 없는 생성자 생성 " << std::endl;
	}

	Base(int val)
		: value{val}{
		std::cout << "Base 인자 1개 생성자 생성" << std::endl;
	}

};

class Derived : public Base
{
private:
	int doubledValue;

public:
	Derived()
		:doubledValue{ 0 }
	{
		std::cout << "Derived 인자 없는 생성자 생성" << std::endl;
	}

	Derived(int val)
		:doubledValue{ 2* val }
	{
		std::cout << "Derived 인자 1개 생성자 생성" << std::endl;
	}
};

int main()
{
	Base b;
	Base b2{ 100 };

	Derived d;//base 인자 없는 생성자 먼저 호출
	Derived d2{ 100 };//base 인자가 없는 생성자가 호출 됨
}
  • 멤버 변수들은 아래와 같은 값을 가지게 되는데 왜 value의 값이 0인가?

위의 그림의 코드를 보면 Derived d2{100}; 을 선언했을 경우 base(int val) 생성자가 호출이 될 것 같지만 실제로는 base() 생성자가 호출이 된다. 이를 우리가 직접 정해줄 수 있다.

기본 클래스의 생성자로 인자 전달


  • 기본 클래스의 어떤 생성자를 호출할지 결정해 줄 수 있어야 함
  • 유도 클래스의 생성자에, 초기화 리스트를 활용해 사용자가 원하는 기본 클래스의 생성자 호출 가능
    • 아무것도 지정하지 않았을 때는 자동으로 기본 생성자가 호출이 된다.
	//Derived(int val)
	//	:doubledValue{ 2* val }
	//{
	//	std::cout << "Derived 인자 1개 생성자 생성" << std::endl;
	//}

	Derived(int val)
		:Base{ val },doubledValue {2 * val}//기본 생성자를 지정 가능함
	{
		std::cout << "Derived 인자 1개 생성자 생성" << std::endl;
	}

위에서 작성했던 코드를 수정해 봤다. Base{ val } 생성자를 호출하게 하기 위해서 직접 지정해 주었다.

  • 기본 클래스 value = 100, 유도 클래스 doubledValue = 200 값을 가지게 된다.

복사 생성자 상속


  • 기본 클래스로부터 상속되지 않음

  • 상속 전과 마찬가지로 컴파일러가 자동생성하지만, 필요한 경우 직접 구현해야함

  • 기본 클래스에서 구현한 복사 생성자 호출 가능

유도 클래스의 복사 생성자


  • 기본 클래스의 복사 생성자를 직접 호출 가능

  • slice 과정을 거침

    • 이게 뭘까..?
    • Base를 사용하는데 Derived 객체를 사용할 수 있음
      • Derived는 항상 Base가 안에 포함이 되어있기 때문에
  • 이동 생성자도 동일하게 동작

예시 코드


#include <iostream>

class Base
{
private:
	int value;
public:

	Base()
		:value{0} 
	{
		std::cout << "Base 인자 없는 생성자 생성 " << std::endl;
	}

	Base(int val)
		: value{val}{
		std::cout << "Base 인자 1개 생성자 생성" << std::endl;
	}

	Base(const Base& other)//복사 생성자
		:value{other.value}
	{

	}

};

class Derived : public Base
{
private:
	int doubledValue;

public:
	Derived()
		:doubledValue{ 0 }
	{
		std::cout << "Derived 인자 없는 생성자 생성" << std::endl;
	}

	Derived(int val)
		:Base{ val },doubledValue {2 * val}//기본 생성자를 지정 가능함
	{
		std::cout << "Derived 인자 1개 생성자 생성" << std::endl;
	}

	Derived(const Derived& other)
		:doubledValue{other.doubledValue}
	{

	}
};

int main()
{
	Derived d1{ 100 };
	Derived d2{ d1 };//base 인자가 없는 생성자가 호출 됨
}
  • 위와 같은 코드가 있을 때 Derived(const Derived& other)를 보면 문제가 하나 있다.

나는 d1의 value의 값인 100으로 복사하고 싶은데 d2의 value의 값이 0으로 자동 초기화가 되는 것이 문제다

문제를 해결하기 위해서는


	Derived(const Derived& other)
		:Base{other},doubledValue{other.doubledValue}
	{

	}

주의!!!

  • 어 ? Base{other}을 선언하면 Base 복사 생성자가 호출이 될것인데 type이 Derived가 아니라 불가능 하지 않나요~?
    • 아니다! 우리가 이전에 상속받은 class는 Base 클래스의 type 또한 가능하다고 배웠다!
  • slicing의 개념
    	Derived(const Derived& other)
    		:Base{other},doubledValue{other.doubledValue}
    	{
    
    	}

위의 코드를 중단점을 찍어서 실행시켜 보자!

  1. Derived의 복사 생성자를 실행 했을 때 other은 value와 doubledValue가 있는 것을 알 수 있다.

  1. 하지만 이후 곧바로 호출된 Base 복사 생성자의 other을 보면 value만 존재하는 것을 알 수 있다.

즉, slice란 유도 클래스로터 기본 클래스의 필요한 부분만 가져와서 사용 할 수 있다는 것이다. 이것이 Base 클래스에서 Derived를 사용 가능한 이유다.

profile
개발로그

0개의 댓글