C++ RTTI와 static_cast, dynamic_cast

vani2의 이미지

c++ rtti기능에 대해 배우다가 의문이 생겼는데요

#include <iostream>
#include <typeinfo>
 
using namespace std;
class Ap
{
	virtual void foo()
	{
	};
};
class Bp : public Ap
{
	virtual void foo()
	{
	};
};
class Cp : public Bp
{
	virtual void foo()
	{
	};
};
 
void foo(const Ap& src);
 
int main(int argc, const char* argv[])
{
	const Bp& c = Cp();
 
	cout << (typeid(c) == typeid(Cp)) << endl; // 1
 
	cout << (typeid(c) == typeid(Bp)) << endl; // 0
	return 0;
}
 
void foo(const Ap& src)
{
	Bp* bp = nullptr;
	Cp* cp = nullptr;
 
	if(typeid(&src) == typeid(bp))
	{
		bp = static_cast<Bp*>(&src);
	}
 
	if(typeid(&src) == typeid(cp))
	{
		cp = static_cast<Cp*>(&src);
	}
}

위와 같은 코드가 있을때 foo함수 내부에서 static_cast대신 dynamic_cast도 사용할 수 있을탠데

rtti가 조건분기에서 src의 타입을 알아내서 알맞은 if문을 실행시키므로 static_cast면 충분할 것 같은데..

그렇다면 dynamic_cast는 어디서 사용되는 건가요?

제 친구에게 물어봤더니

상속을 고려한다면

class A { };
class B : public A { };
class C : public B { };
 
C c;
B *pb = &c;
 
typeid(*pb) == typeid(B); // false

이렇게 되기때문에 static_cast만 사용하는건 위험하다고 하던데..

친구가 제시한 코드의 클래스 선언문에선 virtual함수가 없어서 rtti가 작동이 안될탠데..
그건 너그럽게 넘어가고..

typeid(*pb) == typeid(B); // false

이 부분은 당연한거라고 생각하는데 문제가 된다는게 이해가 안가네요..

질문을 정리하자면
1. dynamic_cast의 사용처는 어디?
2. 처음 코드에서 static_cast를 사용하면 위험하다?
3. 친구가 제시한코드 해석..

klyx의 이미지

우선, 설사 typeid로 dynamic_cast가 할 수 있는 모든 걸 할 수 있다고 해도, 반대로 typeid가 할 수 있는건 dynamic_cast로도 할 수 있기 때문에 dynamic_cast를 어따쓰냐는 질문은 'dynamic_cast를 쓰면 typeid를 쓸 필요가 없죠. 그럼 typeid는 뭐에쓰나요?' 라는 질문과 가치가 같습니다.
한쪽으로 다른 쪽을 할 수 있다고 해서 다른 쪽이 쓸모 없어지는 건 아닙니다. 더 편리한 방법이 있다면 그걸 쓸 수도 있겠죠.
typeid로 일일이 타입을 체크한 후에 static_cast로 캐스팅하는 것보다는 dynamic_cast로 캐스팅하는게 훨씬 깔끔하고 직관적일 수 있습니다.
memcpy가 있는데 왜 strcpy를 쓸까요? strcpy가 있는데 왜 strdup를 쓸까요? 생각해보시기 바랍니다.
더 나아가 typeid로 체크하고 static_cast로 캐스팅하게 되면 같은 변수/타입을 여러번 적어줘야하는데(bp의 typeid를 비교한 후에 bp에 대입), 같은 내용을 중복 적재하다보면 실수할 확률도 높아지죠. 예를 들면 foo()안에서 if 조건안의 대입문이 뒤바뀌는 실수를 할 수도 있습니다(bp의 typeid를 비교한 후에 cp에 대입하는 실수).

그리고 foo()함수를 돌려보시면 아시겠지만 원하시는대로 작동안할겁니다.
pointer는 그자체로 하나의 새로운 변수이지 그것이 가리키고 있는 객체 그 자체가 아닙니다.
typeid(포인터)는 포인터의 타입을 알려줄 뿐, 그것이 무엇을 가리키고 있는지를 알려주지 않습니다.
포인터가 가리키는 타입을 가져오려면 디레퍼런싱을 해야하는데, 지금 nullptr을 가리키고 있기 때문에 typeid(*pb)는 std::bad_typeid 예외를 던질겁니다.
원하는 동작은 아마 typeid(src) == typeid(Bp)처럼 비교해야할 겁니다.

마지막으로 plusb님이 올리신 코드는 컴파일도 안되는 코드니까 테스트도 안해보고 올리신듯 한데(테스트 해보셨으면 foo()에서 bp와 cp모두 결국 null pointer라는걸 금방 알 수 있겠죠), 그건 너그럽게 넘어가겠습니다.

vani2의 이미지

그렇군요.

테스트를 안해본것은 사실입니다.
보고있던 문서를 끝까지 않읽고 질문한 제 실수에 대해서 사과드립니다.

winner의 이미지

A - B, A - C 가 아니라 A - B - C 라고 합시다.
그리고 원하는 바가 B 객체이거나 B 의 파생객체인지 확인하기 위해서 typeid 를 쓰고자 하신다면 B, C 둘 다 확인해봐야 합니다.
만일 상속구조가 늘어나면 비교대상은 계속해서 늘어나게 됩니다.
dynamic_cast 를 쓴다면 B 로 casting 한번만 해보면 됩니다. dynamic_cast 는 객체의 부모 class 를 따라가면서 확인하는 과정을 거칩니다.
즉 객체의 class 가 C 였다면 C - B - A 순서로 올라가면서 casting 이 가능한지 조사해봅니다. 이 경우는 B 에서 멈추겠죠.
그래서 dynamic_cast 는 매우 느려질 수 있습니다.
typeid 로 가능한 요구사항 즉 단순히 정확한 비교 하나를 원하시는 거라면 typeid 쓰시는게 좋죠.
그리고 그 후 casting 이 필요하다면 static_cast 를 쓰는 것은 상당히 바람직합니다.
그러나 dynamic_cast 를 쓴다고 그리 느려지는 것도 아닐 겁니다. Compiler 구현을 그리 멍청하게 하지는 않았을테니까요.
다만 초기 Visual C++ 의 dynamic_cast 는 매우 느렸다고 하더군요. RTTI 기능을 끌 수 있게 한 이유 중 하나였을 겁니다.

vani2의 이미지

그렇군요.

좋은 가이드라인 감사합니다.

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.