업캐스팅과 다운캐스팅에 대한 복습

갑자기 C++가 공부하고 싶어지는 그런 날이 있다. 그런 날이 오늘인거고 가끔씩은 헷갈리고 잊어버릴거 같아서 블로그에 정리하는 김에 포스팅을 남기기로 한다.

#include <iostream>

class Parent1 {
public:
	virtual void speak1() {
		std::cout << "Parent1 speaks" << std::endl;

	}



	virtual ~Parent1() = default;
};



class Parent2 {
public:
	virtual void speak2() {
		std::cout << "Parent2 speaks" << std::endl;
	}


	virtual ~Parent2() = default;
};



class Child : public Parent1, public Parent2 {
public :
	void speak1() override { std::cout << "Child speaks as Parent1" << std::endl; }
	void speak2() override { std::cout << "Child speaks as Parent2" << std::endl; }
	void childOnly() { std::cout << "Child only function" << std::endl; }
};



int main()
{
	Child child;



	Parent1* p1 = &child; //업캐스팅



	Child* childPtr = dynamic_cast<Child*>(p1);

	if (childPtr) {
		childPtr->speak1(); 
		childPtr->speak2();
		childPtr->childOnly();
	}


	Child child2;
	Parent2* p2 = &child2;



	std::cout << "자식 클래스의 메서드 호출" << std::endl;
	p1->speak1();
	p2->speak2(); // 자식 클래스의 메서드 호출
	std::cout << "부모 클래스의 메서드 호출" << std::endl;
	p1->Parent1::speak1();
	p2->Parent2::speak2(); //명시적으로 부모 클래스의 인스턴스를 호출

	Parent1 down_parent1;
	Parent2 down_parent2;
	Child child3;

	Parent1* dp = &child3;
	Parent2* dp2 = &child3;



	//다운 캐스팅 -> 부모 클래스를 자식 클래스의 포인터로 가르키는 것

	Child* kid1 = dynamic_cast<Child*>(dp);
	Child* kid2 = dynamic_cast<Child*>(dp2); //dynamic_cast는 템플릿을 활용하여 자식 클래스의 클래스로 타입을 확정시키고, 부모클래스의 포인터를 인수로 받는다.

	std::cout << "다운 캐스팅" << std::endl;



	kid1->speak1();
	kid2->speak1();






	std::cout << "end" << std::endl;

}

출력 결과
Child speaks as Parent1
Child speaks as Parent2
Child only function
자식 클래스의 메서드 호출
Child speaks as Parent1
Child speaks as Parent2
부모 클래스의 메서드 호출
Parent1 speaks
Parent2 speaks
다운 캐스팅
Child speaks as Parent1
Child speaks as Parent1
end

아시다시피 객체지향에서는 다형성(poly morphism)이라는 개념이 있다.

다형성 하면 나오는 주제가 업캐스팅/ 다운 캐스팅이다.

내가 이해한 대로 적어버리는 개념

업캐스팅: 업캐스팅은 쉽게 말해, 자식 클래스의 인스턴스를 부모 클래스의 포인터로 가르키는 것이다.

즉, 자식의 부모가 여럿인 경우 자식이 어떤 부모를 부를지를 고르는 경우라고 비유하면 쉽다.

Parent1* p1 = &child; //업캐스팅
p1->speak1();

위와 같은 경우 부모클래스의 메서드가 호출 될거 같이 보이지만 실제로는 그렇지 않다.

업캐스팅과 다운캐스팅을 이해함에 있어 우선 정적 바인딩과 동적 바인딩의 개념을 이해해야 한다.

정적 바인딩은 어떤 인스턴스의 메서드가 호출되는 상황에서는 해당하는 클래스의 메서드만 호출된다.

동적 바인딩은 코드를 보면 알 수 있듯이 virtual 키워드를 사용하여 가상 상속을 구현한다.

virtual 키워드가 사용된 함수는 동적 바인딩의 대상이 되는데 즉 컴파일 타임에 호출되는 메서드가 어느 클래스의 메서드인지가 결정되지 않고 런타임 중 결정됨을 의미한다. 따라서 가장 마지막에 상속 받는 자식 클래스의 메서드가 호출 되는 것이다.

따라서 정적으로 부모 클래스의 메서드를 호출하기 위해서는 두 가지 방법이 있는데,

  1. 부모 클래스에 virtual 키워드를 뺀 함수를 넣고 호출
  2. 멤버 연산자(::)를 사용하여 호출

2번의 방법을 사용하면

std::cout << "부모 클래스의 메서드 호출" << std::endl;
p1->Parent1::speak1();
p2->Parent2::speak2(); //명시적으로 부모 클래스의 인스턴스를 호출

정확히 부모 클래스의 메서드가 호출 되게 된다.

따라서 명시적으로 부모 클래스의 멤버를 사용할 수 있는 것이 업캐스팅이 되겠다. 여기에 동적 바인딩이 합쳐지게 되면

#include <iostream>
#include <string>

class Parent1 {
public:
	std::string Parent1str = "안녕하세요 Parent1";
	virtual void speak1() {
		std::cout << "Parent1 speaks" << std::endl;

	}



	virtual ~Parent1() = default;
};



class Parent2 {
public:
	std::string Parent2str = "안녕하세요 Parent2";
	virtual void speak2() {
		std::cout << "Parent2 speaks" << std::endl;
	}


	virtual ~Parent2() = default;
};



class Child : public Parent1, public Parent2 {
public :
	std::string Childstr = "안녕하세요 Child";
	void speak1() override { std::cout << "Child speaks as Parent1" << std::endl; }
	void speak2() override { std::cout << "Child speaks as Parent2" << std::endl; }
	void childOnly() { std::cout << "Child only function" << std::endl; }
};



int main()
{
	Child child;



	Parent1* p1 = &child; //업캐스팅



	Child* childPtr = dynamic_cast<Child*>(p1);

	if (childPtr) {
		childPtr->speak1(); 
		childPtr->speak2();
		childPtr->childOnly();
	}


	Child child2;
	Parent2* p2 = &child2;



	std::cout << "자식 클래스의 메서드 호출" << std::endl;
	p1->speak1();
	p2->speak2(); // 자식 클래스의 메서드 호출
	std::cout << "부모 클래스의 메서드 호출" << std::endl;
	p1->Parent1::speak1();
	p2->Parent2::speak2(); //명시적으로 부모 클래스의 인스턴스를 호출

	std::cout << "업캐스팅과 부모 클래스의 멤버 사용 예제" << std::endl;
	std::cout << p1->Parent1str << std::endl;
	std::cout << p2->Parent2str << std::endl;

	//
	std::cout << "업캐스팅된 포인터를 다운캐스트하여 자식 클래스의 멤버를 사용하는 예제" << std::endl;
	Child* cp = dynamic_cast<Child*>(p1);
	Child* cp2 = dynamic_cast<Child*>(p2);

	p1->speak1();
	p2->speak2();
	std::cout << "부모 클래스의 메서드 호출 시도 " << std::endl;
	p1->Parent1::speak1();
	p2->Parent2::speak2();
	std::cout << "다운캐스팅된 포인터를 사용하여 멤버 호출1" << std::endl;
	std::cout << p1->Parent1::Parent1str << std::endl;
	std::cout << p2->Parent2::Parent2str << std::endl;
	std::cout << "다운캐스팅된 포인터를 사용하여 멤버 호출2" << std::endl;
	std::cout << p1->Parent1str << std::endl;
	std::cout << p2->Parent2str << std::endl;


	//
	Parent1 down_parent1;
	Parent2 down_parent2;
	Child child3;

	Parent1* dp = &child3;
	Parent2* dp2 = &child3;



	//다운 캐스팅 -> 부모 클래스를 자식 클래스의 포인터로 가르키는 것

	Child* kid1 = dynamic_cast<Child*>(dp);
	Child* kid2 = dynamic_cast<Child*>(dp2); //dynamic_cast는 템플릿을 활용하여 자식 클래스의 클래스로 타입을 확정시키고, 부모클래스의 포인터를 인수로 받는다.

	std::cout << "다운 캐스팅" << std::endl;



	kid1->speak1();
	kid2->speak1();






	std::cout << "end" << std::endl;

}

이와 같이 다운캐스팅은 업캐스팅된 포인터를 다시 자식 클래스로 캐스팅하고 동적 바인딩을 결합하면 자식 클래스의 메서드도 호출 가능하고 조상 클래스의 멤버도 사용가능해진다.