언리얼 - C++ 37 : 상속

김정환·2025년 3월 26일

Unreal C++

목록 보기
36/37

객체지향 언어의 특징

  1. 캡슐화
    • 목적으로 하는 기능에 필요한 멤버 변수, 기능을 하나의 클래스로 묶어서 구현하는 것.
    • 은닉성 : 외부에서 접근이 불필요한 중요 멤버에 대해서는 접근시키지 못하게 하는 것.
  2. 상속
  3. 다형성
  4. 추상화

상속 Inherit

class CParent
{
private:
	int m_i;	// 4 byte
public:
    CParent()
    	: m_i(0)
    {}
    ~CParent();
};

// 클래스 상속
class CChild : public CParent
{
private:
	float m_f;	// 4 byte
public:
	CChild()
    	: m_f(0f)
	{}
};
  • 어떤 클래스가 상속을 받는다. => 해당 클래스가 다른 클래스의 기능을 가져온다.
  • 상위 클래스의 기능을 토대로 새로운 기능을 확장시킨다.

상속의 이점

  • 코드의 재사용성
int main()
{
	CParent parent;	// 상위클래스
    
    CChild child; // 하위클래스 : 상속받은 클래스
    
	return 0;
}

상속 시, 메모리 구조

  • CParent : 4 byte
  • CChild : 8 byte
    • CParent 부분 (4 byte) + 새로운 CChild 부분 (4 byte) = 8 byte
  • 상속을 이용해 구현한 클래스의 메모리 구조는 상위 클래스가 메모리상으로 먼저 배치됨.
    • 순차적으로 부모 -> 자식 순으로
    • 메모리가 생기는 순서는 매우 중요함.
CChild child; 
선언 시, 메모리 할당
[1. CParent (4 byte) | 2. CChild (4 byte)]

protected 접근 제한자

  • 기존에 외부 클래스에서 다른 클래스의 멤버에 접근하기 위해선 public 을 사용했음.
  • 캡슐화를 위해서 멤버 변수는 private로 선언하고
    Setter 함수를 public으로 선언해왔음.
  • 상위 클래스의 private 멤버는 자식 클래스에서도 접근하지 못함.
    • 하위 클래스의 구성에 포함하고 있으나 직접 접근할 순 없는 것.
    • public으로 선언해야하나? => 접근은 할 순 있으나, 외부에도 공개될 것.
    • 은닉성의 문제가 발생할 여지가 있음.
  • 이런 상황을 위해서 하위 클래스에선 접근할 수 있으나 외부에선 접근하지 못하도록
    protected 접근 제한자를 사용.

생성자 작동 방식

  • 부모 클래스의 기능은 자식 클래스에서 사용할 수 있음.
    • 즉, 자식 클래스에서 부모 클래스의 함수를 사용할 수 있음.
  • 그렇다면 생성자의 호출은?
    CChild child; // : CParent
    • 객체가 생성되면, 클래스는 각자 자신의 생성자를 호출함.
    class CChild : public CParent
    {
    private:
    		float m_f;
    public:
    		CChild()
          	: m_f(0.f)	// 여기까진 좋음
              , m_i(1)	// 이 부분은 여기서 호출되면 안됨.
          {}
    }
    • 위의 경우 생성자에서 상위 클래스의 멤버 변수를 초괴화해주는데,
      이렇게 사용하면 오류가 발생함.
      • 생성자 내에서 값을 넣는 것은 됨.
        • 근데 이보다 더 나은 방법이 있음.

생성자의 호출 순서

  • CChild 선언시 주체가 되는 것은 CChild임.
    • 그러면 클래스의 생성자는? => 부모 생성자가 먼저 호출됨.
    • 메모리 배치 구조를 보면 부모가 먼저 배치된다고 했음.
class CChild : public CParent
  {
  private:
  		float m_f;
  public:
  		CChild()
        	: m_f(0.f)	// 여기까진 좋음
            , m_i(1)	// 이 부분은 여기서 호출되면 안됨.
        {}
        
        CChild()
        	: CParent()	 // 이 형태가 생략된것
            , m_i(1)	
        {}
        
  }
  • 부모의 생성자가 먼저 마무리된 이후에 자식 생성자가 진행됨.
  • 호출 순서상 자식이 먼저되는 것은 맞으나, 실행은 부모가 먼저됨

상속 시, 생성자 호출 및 동작 순서 정리

  • 자식 or 부모 클래스는 상속 관계에서는 다른 클래스의 멤버를 초기화할 수 없음
    • 자기 할 일은 자기가 하는 것. : 초기화 한정
  • 생성자 호출 순서
    • 자식 => 부모 순서로 올라감
  • 생성자 실행 순서, 초기화 동작 순서
    • 부모 => 자식 순서

소멸자의 경우

  • 실행, 호출 모두 자식 파트가 실행되고, 부모 파트가 실행됨.
    • 자식 => 부모 순서

오버라이딩 Overriding

  • 오버로드와 오버라이딩이 이름이 유사한 것을 보고 개념을 아는지 묻는 면접 질문이 자주 있음
    • 오버로드 Overload
      • 어떤 함수를 매개변수를 달리하여 다양한 상황에서 쓸 수 있도록 작성한 것
    • 오버라이드 Override
      • 상속 관계에서 상위 클래스의 추상, 가상 함수를 하위 클래스에서 재정의 또는 확장하는 것
class CParent 
{
public:
	void Output() 
	{
		cout << "output: parent" << endl;
	}
};

class CChild : public CParent
{
~~
};


int main()
{
	CParent parent;
    parent.Output();	// "output: parent"
    
    CChild child;
    child.Output(); 	// "output: parent"
    
	return 0;
}
  • 자식 클래스는 당연히 부모 클래스의 함수를 호출할 수 있음.

class CParent 
{
public:
	void Output() 
	{
		cout << "output: parent" << endl;
	}
};

class CChild : public CParent
{
public:
	void Output() 
	{
		cout << "output: child" << endl;
	}
};
  • 상속받은 부모클래스의 함수를 재정의 override
    • overload가 아닌 덮어쓰는 것
int main()
{
    CParent parent;
    parent.Output();	// output: parent"

    CChild child;
    child.Output();		// "output: child"
    child.CParent::Output(); // output: parent"
    
	return 0;
}
  • 둘 다 있을 땐, 자식 쪽에 구현된 것이 호출됨.
    없다면 부모의 함수가 호출될 것.
  • child.CParent::Output();
    굳이 명시적으로 부모 클래스의 함수를 호출할 수 있음.
    => 이렇게 할거면 애초에 오버라이딩을 안할 것

오버라이드는 왜 쓸까?

  • 상속을 사용하는 상황 예시.
  • 동물 => 모든 동물들이 공통적으로 사용할 기능을 정리해둘 것임.
    • 조류 : 동물이 하는 동작 + 조류만의 기능
    • 포유류 : 동물이 하는 동작 + 포유류만의 기능
    • 파충류 : 동물이 하는 동작 + 파충류만의 기능
  • 모든 설계가 다 맞아 떨어지면 좋은데 항상 예외가 생김.
    • 분명 파생되는 모든 것들이 대다수에는 해당되더라도 일부는 조금 다르게 동작하는 경우가 생김.
    • 이를 위해서 오버라이딩이 필요한 것.
profile
만성피로 개발자

0개의 댓글