18.10 Dynamic casting

주홍영·2022년 3월 21일
0

Learncpp.com

목록 보기
181/199

https://www.learncpp.com/cpp-tutorial/dynamic-casting/

lesson 8.5 -- Explicit type conversion (casting) and static_cast,에서
우리는 casting에 대한 개념에 대해서 짚고 넘어갔다.

이번 lesson에서는 dynamic cast라는 또 다른 타입으 casting에 대해서 살펴본다

The need for dynamic_cast

polymorphism을 다룰 때 우리는 종종 base class의 pointer를 가지고 있을 때 derived class의 정보에 접근하고 싶을 때가 있다

다음 예시를 살펴보자

#include <iostream>
#include <string>

class Base
{
protected:
	int m_value{};

public:
	Base(int value)
		: m_value{value}
	{
	}

	virtual ~Base() = default;
};

class Derived : public Base
{
protected:
	std::string m_name{};

public:
	Derived(int value, const std::string& name)
		: Base{value}, m_name{name}
	{
	}

	const std::string& getName() const { return m_name; }
};

Base* getObject(bool returnDerived)
{
	if (returnDerived)
		return new Derived{1, "Apple"};
	else
		return new Base{2};
}

int main()
{
	Base* b{ getObject(true) };

	// how do we print the Derived object's name here, having only a Base pointer?

	delete b;

	return 0;
}

위 프로그램에서 getObject 함수는 항상 Base pointer를 반환한다. 그러나 pointer는 Base 혹은 Derived object 둘 다 pointing할 수 있다. 이러한 상황에서 pointer가 Derived object를 pointing하는 경우 우리는 어떻게 Derived::getName()을 호출할 수 있을까?

한가지 방법은 virtual function을 사용하는 것이다

하지만 Base object를 pointing하는 Base pointer의 경우에는 어떻게 해야하나?
이는 의미가 없다. 게다가 우리는 Base class를 오염시킬 수도 있다

우리는 c++에서 implicitly하게 Derived pointer를 Base pointer로 convert해주는 것을 알고 있다. 이러한 과정을 upcasting이라고 한다

그러나 Base 포인터를 Derived 포인터로 다시 변환하는 방법이 있다면 어떨까? 그렇다면 우리는 Derived::getName()을 직접적으로 호출할 수 있을 것이다. virtual에 상관없이

dynamic_cast

c++은 casting operator로 dynamic_cast 라는 것을 지원한다
이는 이러한 목적으로 사용가능하다.
dynamic casting에는 몇 가지 다른 기능이 있지만 dynamic casting의 가장 일반적인 용도는 Base 클래스 포인터를 Derived 클래스 포인터로 변환하는 것입니다.
이를 upcasting의 반댓말인 downcasting 이라고 한다

dynamic_cast는 static_cast 처럼 사용하면 된다
여기 예시가 있다

int main()
{
	Base* b{ getObject(true) };

        Derived* d{ dynamic_cast<Derived*>(b) }; // use dynamic cast to convert Base pointer into Derived pointer

        std::cout << "The name of the Derived is: " << d->getName() << '\n';

	delete b;

	return 0;
}

getObject로 수령한 Base pointer는 Derived object를 pointing하고 있다
그리고 이를 dynamic_cast로 다시 Derived pointer로 downcasting 하고 있다

dynamic_cast failure

위의 예시는 잘 작동한다. 왜냐하면 b object가 Derived Object를 pointing하는 pointer이기 때문이다

그러나 그렇지 않은 경우에는 어떻게 될 것인가?
이는 getObject의 argument를 false로 바꿈으로 간단하게 테스트 할수 있다
만약 우리가 이러한 dynamic_cast를 시도한다면 아마 실패할 것이다

만약 dynamic_cast가 실패하면 결과는 null pointer일 것이다

따라서 우리는 다음과 같이 코드를 작성해야 한다

int main()
{
	Base* b{ getObject(true) };

	Derived* d{ dynamic_cast<Derived*>(b) }; // use dynamic cast to convert Base pointer into Derived pointer

	if (d) // make sure d is non-null
		std::cout << "The name of the Derived is: " << d->getName() << '\n';

	delete b;

	return 0;
}

if (d)를 통해 null pointer인지 체크를 하고 아닌 경우에 d->getName()의 접근을 허용한다

Rule

Always ensure your dynamic casts actually succeeded by checking for a null pointer result.

dynamic_cast는 consistency checking을 runtime에서(conversion이 가능한지 보장하기 위해서) 하므로 성능의 저하를 야기시킨다

또한 여기 dynamic_cast를 통한 downcasting이 실패할 수 잇는 경우가 있다

  1. With protected or private inheritance.
  2. For classes that do not declare or inherit any virtual functions (and thus don’t have a virtual table).
  3. In certain cases involving virtual base classes (see this page for an example of some of these cases, and how to resolve them).

Downcasting with static_cast

Downcasting은 static_cast로도 할 수 있다. 가장 큰 차이점은 static_cast의 경우 runtime type check를 하지 않는다. 이는 static_cast를 사용하는 것이 더 빠르지만 위험해진다는 것을 뜻한다.
Base 포인터가 Derived object를 pointing하는 것이 아닌 경우에도 Base pointer에서 Derived pointer로 변환은 성공한다. 이는 우리가 의도치 않은 동작이다

따라서 우리가 static_cast를 통해 Downcasting을 하고 싶다면 먼저 Derived class object를 pointing하고 있는지 확인해야 한다
다음의 예시에서는 virtual function으로 이를 확인한다

#include <iostream>
#include <string>

// Class identifier
enum class ClassID
{
	base,
	derived
	// Others can be added here later
};

class Base
{
protected:
	int m_value{};

public:
	Base(int value)
		: m_value{value}
	{
	}

	virtual ~Base() = default;
	virtual ClassID getClassID() const { return ClassID::base; }
};

class Derived : public Base
{
protected:
	std::string m_name{};

public:
	Derived(int value, const std::string& name)
		: Base{value}, m_name{name}
	{
	}

	const std::string& getName() const { return m_name; }
	virtual ClassID getClassID() const { return ClassID::derived; }

};

Base* getObject(bool bReturnDerived)
{
	if (bReturnDerived)
		return new Derived{1, "Apple"};
	else
		return new Base{2};
}

int main()
{
	Base* b{ getObject(true) };

	if (b->getClassID() == ClassID::derived)
	{
		// We already proved b is pointing to a Derived object, so this should always succeed
		Derived* d{ static_cast<Derived*>(b) };
		std::cout << "The name of the Derived is: " << d->getName() << '\n';
	}

	delete b;

	return 0;
}

번거롭게 이러한 고려를 하고싶지 않다면 그냥 dynamic_cast를 사용하자

dynamic_cast and references

위의 모든 예들은 pointer의 경우에 dynamic casting에 (더 일반적임) 대해서 보여주고 있지만
dynamic_cast는 reference에도 사용할 수 있다. 이것은 dynamic_cast가 포인터와 함께 작동하는 방식과 유사하게 작동한다

#include <iostream>
#include <string>

class Base
{
protected:
	int m_value;

public:
	Base(int value)
		: m_value{value}
	{
	}

	virtual ~Base() = default;
};

class Derived : public Base
{
protected:
	std::string m_name;

public:
	Derived(int value, const std::string& name)
		: Base{value}, m_name{name}
	{
	}

	const std::string& getName() const { return m_name; }
};

int main()
{
	Derived apple{1, "Apple"}; // create an apple
	Base& b{ apple }; // set base reference to object
	Derived& d{ dynamic_cast<Derived&>(b) }; // dynamic cast using a reference instead of a pointer

	std::cout << "The name of the Derived is: " << d.getName() << '\n'; // we can access Derived::getName through d

	return 0;
}

c++은 null reference가 없으므로 casting에 실패하면 std::bad_cast가 반환된다

dynamic_cast vs static_cast

신입 프로그래머는 가끔식 static_cast와 dynamic_cast의 사용에 혼란스러워 한다
답은 매우 간단하다. downcasting이 아니면 static_cast를 사용하고
만약 downcasting인 경우 보통 dynamic_cast가 적절한 선택이다
하지만 virtual로 이를 우회하는 방법도 고려해야 한다

Downcasting vs virtual functions

몇몇 개발자들은 dynamic_cast가 나쁘고 나쁜 클래스 디자인을 나타낸다고 믿는다
대신에 virtual function을 사용하라고 말한다

일반적으로 downcasting 보다는 virtual function을 사용하는 것이 선호된다
하지만 downcasting이 더 나은 선택인 경우도 있다

  • When you can not modify the base class to add a virtual function (e.g. because the base class is part of the standard library)
  • When you need access to something that is derived-class specific (e.g. an access function that only exists in the derived class)
  • When adding a virtual function to your base class doesn’t make sense (e.g. there is no appropriate value for the base class to return). Using a pure virtual function may be an option here if you don’t need to instantiate the base class.
profile
청룡동거주민

0개의 댓글