C++ 7일차 - 3

JUSTICE_DER·2023년 2월 9일
0

C++

목록 보기
12/20

C++의 다형성, 가상함수, 다운캐스팅에 대해 추가 공부해보자

두들낙서라는 유튜버의 영상을 찾아보게 되었고,
78강부터 쭉 보면 원하는 답을 찾을 수 있을 것 같았다.

두들 78강 - 가상함수와 동적바인딩

여기서 가상함수에 대한 자세한 설명을 들을 수 있었다.

class CParent {
protected:
	int i;
public:
	CParent() : i(0) {};
	~CParent() {};

	void setInt(int a) {
		i = a;
	};

	virtual void Out() {
		printf("부모\n");
	};
};

부모객체인 CParent에 virtual을 위처럼 설정했었고,

CParent parent;
CChild child;

CParent* tmpObj = nullptr;

tmpObj = &child;
tmpObj->Out();

위의 실행 결과가 당연히 "자식" 이렇게 출력될 것인데,
virtual이라는 키워드의 뜻은, 실행될 수도 안될 수도 있다
라는 의미로,
컴파일러의 입장에선, 해당 tmpObj->Out( )는 그냥
부모의 Out을 호출하면 되는데,
virtual이 붙어있으면, 컴파일러는 고민하게 된다.
그리고 실행됨에 따라, 해당 tmpObj에는 child가 들어가있고,
해당 child는 CChild의 자료형으로 만들어져있다는 것을 알게되는 순간, CChild의 Out()을 실행하는 것으로 인식하게 된다.
== 동적바인딩

virtual을 붙이지 않으면 정적바인딩이 일어나고,
정적바인딩이란, 실행시간 전에 미리 일어나고,
실행시간에는 그대로 유지되는 바인딩
반대로,
동적바인딩은 실행시간에 실시간으로 이루어지는 바인딩.


82강 - 업캐스팅

자식을 부모의 자료형으로 변환하는 것을 의미,
이전에 CParent* parent = &child; 이런것과 같다.

부모를 남자, 자식을 총각이라는 단어의 개념으로 본다고 하자.
총각이라는 단어는 남자라는 개념도 포함되면서 더 세부적으로 결혼을
하지 못했다 라는 개념도 포함되어있다.

모든 남자를 총각이라고 볼 수 있는가?
-결혼한 남자가 있을 수 있으므로 안된다.
모든 총각을 남자라고 볼 수 있을까?
-된다.
그래서 자식을 부모에 업캐스팅 하는 것이 가능하고,
명시적으로 표시하지 않고 암묵적으로 진행이 된다.

7-2의 설명에도 있을텐데,
자식의 - 부모/자식 메모리가 연속적으로 할당되기 때문에 가능하다.


83강 - 다운캐스팅(static_cast)

반대로
부모에서 자식으로 내려가는 것을 다운캐스팅이라고 한다.

방법은 static_cast / dynamic_cast이다.

static_cast 정적으로 cast한다는 뜻으로,
다운캐스팅만을 위해 쓰는 예약어는 아니다.

class A{
	int a = 1;
}
class B{
	float b = 2.0f;
    
    void bbb(){
    	printf("ㅠㅠㅠ);
    }
}

간단한 A와 B클래스를 만들었다.

A* _Obj1 = new B;
_Obj1->b = 10.0f;

위의 코드가 가능할까?
불가능하다.
그 이유는 B객체를 업캐스팅하여 A의 자료형이기 때문이다.

A* _Obj1 = new B;
B* _Obj2= (B*)_Obj1; //명시적 형변환
_Obj2->f;

위의 코드는 실행이 된다.
하지만, 매우 원시적인 방법이고,

A* _Obj1 = new B;
B* _Obj2= static_cast<*B>_Obj1; //명시적 형변환
_Obj2->f;

static_cast가 여기서 쓰인다고 한다.
static_cast는 만들어진대로 판단하기에 정상적인 범주내의 자료형의 형변환만 가능하게 해준다. 업/다운캐스팅, int를 float로 등등

하지만 static_cast는 상당히 잘못 쓰일 수 있다.

A* _Obj1 = new B;
C* _Obj2= static_cast<*C>_Obj1;

위처럼 A의 자식이 B와 C가 있다면,
static_cast는 해당 _Obj1에 실제로 어떤 자료형 값이 들어있는지는 상관없고,
현재 형변환하려는 자료형이, 지금 Obj1의 자료형인 A의 부모인가 자식인가
즉, 업인가 다운인가만 확인하고 맞으면 변환시킨다.

84강 - 다운캐스팅 (dynamic_cast)

CParent parent;
CChild child;

CParent* tmpObj = nullptr;

tmpObj = &child;
tmpObj->Out();

위의 코드로 동적바인딩을 보았었다.
Out이라는 함수가 virtual로 부모에 구현이 되어있다면,
해당 함수는 동적바인딩 된다.
해당 tmpObj->Out( )이 호출될 때, tmpObj에 child 클래스임을 알고 child의 Out을 실행할 것이다.

//부모
class CParent {
public:
	int a = 10;
    
    //가상함수
	virtual void Func() {

	}
};


//자식
class CChild : public CParent {
public:
	float b = 20.0f;
    
	void Func() {

	}
};


int main()
{
	CParent parent;
	CChild child;

	//업캐스팅
	CParent* tmpObj = &child;

	std::cout << sizeof(CParent) << std::endl;
	std::cout << sizeof(CChild) << std::endl;
}

위를 출력하면
8과 12가 나온다.

virtual을 빼면,
4와 8이 나온다.

부모처럼 가상함수가 하나라도 존재하는 클래스는 다형클래스라고 본다.
virtual을 추가한다면, sizeof(CParent), sizeofCChild)
해당 클래스와 상속받은 클래스 자료형의 크기가 증가하는 것을 볼 수 있는데,
같이 커지는 이유는 아래 그림을 보면 추리할 수 있다.
어딘가를 가리키는 포인터에 대한 추가 공간을 의미하고,
해당 포인터가 가리키는 공간에는, 클래스에 대한 정보가 기록된다.

이런느낌으로 있는 셈이다.

객체가 생기면 해당 포인터는 클래스에 대한 추가정보가 있는 메모리를 가리킨다.

CParent* doo = new CChild; 

위처럼 업캐스팅 했을때는 어떻게 동작할까?


위처럼 동작한다.
doo가 가리키는 공간은
실제로는 CChild 객체이지만, 담을 수 있는 공간은 CParent크기이다.
그래서 해당 자료형처럼 사용하다가
런타임에 해당 포인터를 참조하여 해당 객체의 원래 자료형이 무엇이었는지 확인할 수 있게 된다.

RTTI(RUN-TIME-TYPE-IDENTIFICATION)

그래서 dynamic_cast라는 것은 동적 형변환 방식으로,
동적 형변환을 사용하기 위해서는 RTTI기능을 사용해야하는데,
그러기 위해선 클래스가 다형클래스여야하고,
다형클래스이려면, 반드시 부모 클래스에 virtual함수가 하나라도 있어야한다.

    //다운캐스팅
	CChild* castedChild = dynamic_cast<CChild*>(tmpObj);
	if (nullptr != castedChild) {
		castedChild->onlyChildFunc();
	}
	else {
		printf("캐스팅 안됨, 방어적인 코딩!");
	}

그래서 실 사용은 위처럼 사용하고,
다운캐스팅으로 바꾸려는 자료형이 실제로 맞구나라는걸 확인해야만
다운캐스팅이 행해진다.
static_cast 였으면 그냥 상속관계이므로 바로 cast했을 것
맞지 않다면, nullptr을 반환하게 된다.

여기서 tmpObj, castedChild 모두 포인터이다.
dynamic_cast는 RTTI를 동반하여, 해당 포인터로 가서 정보를 받아오고,
값까지 같은지 분석해야 하기 때문에 성능적인 부분에서 좋지않다.

궁금증이 85%정도 해소된 것 같다.
다형성에 대해서만 추가로 보면 될 듯 한데
강의가 설명이 좋아서 현재 84강까지 보았는데,
89강 정도까지 보면 많은 도움이 될 듯 하다.
두들낙서

생각보다 C++에 많은 시간이 걸렸는데, 내일까지만 딱 듣고,
남은 7일동안 코테준비에 들어간다.

profile
Time Waits for No One

0개의 댓글