상세 컨텐츠

본문 제목

C++언어 강좌(공부) #14장

카테고리 없음

by 바래다주기 2024. 5. 7. 05:33

본문

14. 다형성

다형성 : 다양한 형태를 가지는 성질, 부모가 자식의 매서드를 사용하는 문법

  1. 오버라이딩 : 오버라이딩 조건
    1. 상속 관계에서만 성립  
    2. 부모/자식 간에 완전 동일하게 선언된 형태의 함수 필요  
    3. 오버라이딩 하는 함수 앞에 virtual 키워드 필요  

virutal 가상 함수 : virtual 키워드를 사용하여 함수 생성 시 가상 함수 포인터와 가상 함수 테이블이 생성된다.
                              가상 함수 포인터는 포인터이므로 4Byte의 공간 차지
                              가상 함수 테이블은 가상 함수 포인터 들의 주소를 저장
                              런 타임에 동작하는 문법으로, 가지고 있는 테이블에 따라 어떤 함수를 호출할지 결정
                              => 동적 바인딩, 재정의 라고 함
                             가상 함수 포인터와 가상 함수 테이블은 상속 시 자식에게 상속됨

오버라이딩의 기능

  1. 재정의 : 자신이 가진 테이블의 함수를 따른다
  2. 은닉화 : 각자 다른 포인터와 테이블을 소유한다

가상 소멸자 : 부모 타입 포인터로 선언된 변수에 자식 타입으로 동적 할당된 객체에 대한 할당 해제?

  1. 정적 바인딩 방식 : 부모 타입에 따르므로, 부모 타입의 해체만 일어남
  2. 동적 바인딩 방식 : 자식 타입에 따르므로, 자식을 해제한 후 부모를 해제함
    -> 클래스의 소멸자에 virtual을 붙여서 사용한다면 하위 클래스의 소멸자까지 호출
  1. 다운 캐스팅

virtual 사용 시의 문제점 두 가지 : 1. 오버라이딩을 사용하기 위해 부모 클래스에도 같은 형태의 함수를 선언해야 함
                                                      2. 부모 클래스에 가상 함수가 너무 많아짐

 

오버라이딩을 사용하기 위해 부모 클래스에도 같은 형태의 함수를 선언해야 함
해결 -> 순수 가상 함수를 활용한 추상 클래스 생성

 

순수 가상 함수 : 오버라이딩 목적으로 만들어진 함수에 대해 비어있는 몸체를 선언하는 방식이아닌 0으로 초기화를 진행                             하는 방식
ex)
virtual void Func() = 0;

 

주의점! 1. 해당 함수는 선언부만 존재하는 미완성 함수로, 오버라이딩 목적으로만 사용
             2. 부모에게 순수 가상 함수가 존재할 경우, 자식 클래스 전체에 해당 함수를 선언해야 함 해당 주의점은 사용 여부                   에 관계가 없다.

 

추상 클래스 : 1. 순수 가상 함수가 하나라도 존재하는 클래스
                      2. 추상 클래스는 객체 생성과 선언이 불가능하다
                      3. 순수 가상 함수는 존재하지 않으며, 해당 이름을 가진 함수의 존재만 알리는 역활이므로 메모리에 할당 할                            수 없다
                          => 문제 1 해결, But 자식 클래스에 함수를 잔뜩 선언해야함

 

부모 클래스에 가상 함수가 너무 많아짐
              해결 => 오버라이딩 대신 다운 캐스팅을 활용

 

기존의 캐스팅(C)
ex)
CObj* Obj = new CPlayer;
(CPlayer*)Obj->Render();

 

강제 형 변환으로써.....
상속관계에 신경 쓰지 않고 서로 정체를 모르는 객체의 타입으로도 변환 가능
-> 따라서 캡슐화, 은닉화 등의 객체 지향 문법이 파괴 된다
-> 캐스팅은 불안정한 문법이다

 

RTTI(런 타임 타입 정보)
런타임 시 객체의 자료형에 관한 정보를 드러내는 C++ 매커니즘

  1. typeid => 런타임 시 객체의 type 확인
    ex)
    typeid(CObj*).name(); => 런타임 중 현재 자료형의 타입을 반환
  2. dynamic_cast => 런 타임시 캐스팅하여 논리/문법적 문제 검토
    ex)
    dynamic_cast<CPlayer*?(m_Obj)->Render(); => CPlayer* 타입으로 형 변환

다운 캐스팅 : 상위 Type에서 하위 Type으로 캐스팅(부모 형 -> 자식 형)

업 캐스팅 : 하위 Type에서 상위 Type으로 캐스팅(자식 형 -> 부모 형)

안전한 캐스팅 : 캐스팅 상태의 존재, 정보에 대해 아는 케이스(자식 -> 부모)

위험한 캐스팅 : 상대의 존재와 정보에 대해 알지 못하는 상태로 캐스팅

 

안전한 다운 캐스팅 예시
ex)
CObj -> 부모 형
CPlayer -> 자식 형

 

CObj* m_Obj = new CPalyer;
dynamic_cast<CPlayer*>(m_Obj)->Render();

 

CObj -> 부모 형
CPlayer -> 자식 형

 

CObj* m_Obj = new CPlayer;
CPlayer* m_Obj2 = dynamic_cast<CPlayer*>(m_Obj);
m_Obj2->Render();

 

안전한 업 캐스팅 예시
ex)
CObj -> 부모 형
CPlayer -> 자식 형

 

CPlayer* m_Player = new CPlayer;
CObj* m_Obj = m_Player; // => 묵시적 형 변환

 

CObj* m_Obj2 = dynamic_cast<CObj*>(m_CPlayer); // => 명시적 업 캐스팅

 

위험한 캐스팅 예시
ex)
CObj -> 부모형
CPlayer -> 자식형

 

CObj* m_Obj = new CObj;
CPlayer* m_Player = dynamic_cast<CPlayer*>(m_Obj);

 

C++의 캐스팅 문법 네 가지

  1. static_cast 2. dynamic_cast 3. const_cast 4. reinterpret_cast

static_cast : static_cast(m_Obj);
                     유일하게 컴파일 시점에 동작하는 정적 캐스팅 방식
                     일반 캐스팅과 달리 상속 관계에서만 작동하는, 논리적인 캐스팅
                     동작 원리는 일반 캐스팅과 같다.
                     부모 타입으로 생성된 부모 객체를 자식 타입으로 재생성?


ex)
CObj* m_Obj = new CObj;
CPlayer* m_Player = static_cast<CPlayer*>(m_Obj);

 

But! static_cast 사용 시 상속 관계임을 확인하므로 동작하긴 함 => 과연 안전한 행위인가?
위험한 캐스팅의 예시와 동일하다 => static_cast는 상속 여부만 파악하며 정적 타이밍에 동작하므로 해당 문제에 대한 검                                                               증을 할 수 없다

 

dynamic_cast : dynamic_cast<CPlayer*>(m_Obj);

                          가상 함수가 단 하나라도 존재하는 클래스에 한해서만 사용이 가능                                                                                        한 문법
                          => 부모 클래스의 소멸자는 무조건 virtual화(오버라이딩 참조)되므로 사용에 문제 없음
                          클래스 포인터 전용 형 변환 방식으로, 원시 자료형에는 사용이 불가능하다
런 타임에 안전한 캐스팅인지 검사를 진행하며 위험한 다운 캐스팅의 경우 NULL값을 반환한다
static_cast에서 검사하지 못하는 부분까지 검사를 진행하여 보다 안전하지만, 연산 속도는 떨어진다
안전성이 확보 된 경우 static_cast 사용 문제점 파악이 목적인 경우 dynamic_cast 사용

 

const_cast : const int* p = &iNumber;
                      int* p2 =const_cast<int*>(p);
                      일시적으로 const를 제거하는 키워드
                      위 코드와 같은 경우에 p에 대한 값은 변경이 불가능 하지만 아래처럼
                      const_cast를 진행한 후에는 해당 값의 변경이 가능해진다
                      포인터 및 래퍼런스에만 사용이 가능하다

 

reinterpret_cast : int iNumber = 65;
                              char* ptr = reinterpret_cast<char*>(&iNumber);
                              cout << ptr << endl;
                              const를 제외한 모든 포인터 형으로의 형 변환 허용
                             논리적 / 문법적으로 따지지 않음 -> 이러한 이유로 잘 사용하지 않는 문법