'RTTI'에 해당되는 글 3건

  1. 2006.11.21 C++ 형변환
  2. 2006.11.20 CRuntimeClass
  3. 2006.11.20 동적 생성 지원 클래스 만들기


C++ 형변환

Development/C / C++ 2006. 11. 21. 17:20
출처 : http://keegan.tistory.com/trackback/259

1. static_cast

C++언어에서 캐스트(cast) 연산은 가장 보편적으로 사용되는 연산 입니다. 그러나 이것 만큼 정확한 의미를 모르고 사용되는 연산또한 없을 것입니다. Visual C++ 가 제공하는 캐스트는 크게 묵시적 캐스트(implicit cast)와 명시적캐스트(explicit cast) 두 가지로 나눌 수 있습니다. 묵시적 캐스트는 문법적인 키워드가 존재하지 않습니다. 이에 반해명시적 캐스트는 문법적인 키워드를 가지고 있는데, 여기에는 (), static_cast, const_cast,reinterpret_cast , dynamic_cast 다섯 가지가 있습니다. 이것에 대해 하나 하나 살펴 보겠습니다.

static_cast

static_cast 는 묵시적 캐스트(implicit cast)와 일차적으로 같습니다. 타입 변환이 필요한 상황에서 특별히 캐스트 연산자를 사용하지 않을 때, 컴파일러는 묵시적 캐스트를 수행 합니다.

다음 코드를 보겠습니다.

int i = 65;

char c = i;

위의 코드는 정수 i 변수의 값을 문자 타입 변수 c 에 대입 합니다. 이때 컴파일러는 묵시적 캐스트(implicit cast)를 수행합니다.

컴파일러의 의한 묵시적 캐스트(implicit cast)는 ‘허용’ 과 ‘컴파일러에 의한 값 변환’ 두 가지로 볼 수 있습니다. ‘허용’ 이라 하는 것은 컴파일러가 오류를 발생 시키는 않고 컴파일 한다는 뜻입니다.

그리고 ‘컴파일러에 의한 값 변환’ 은 값을 변환하기 위하여 컴파일러가 만들어 내는 기계어 코드를 의미 합니다. 그런데 컴파일러가 값을 변환하기 위하여 만들어 내는 기계어 코드는 당연히 컴파일 시점일 될 것입니다.

앞에서 static_cast 가 묵시적 캐스트(implicit cast)와 일차적으로 같다고 했습니다. 즉 static_cast도 묵시적 캐스트와 같이 ‘허용’ 과 ‘컴파일러에 의한 값 변환’ 이라는 두 가지 관점이 있고, ‘컴파일러에 의한 값 변환’ 을위하여 컴파일러가 기계어 코드를 만들어 내는 시점이 컴파일 시점 입니다.

그래서 이것을 정적 캐스트(static cast) 라고 부릅니다.


그럼 왜 굳이 묵시적 캐스트(implicit cast)도 있는데 정적 캐스트(static cast)를 사용할 까요?

문법적 엄격함 입니다. 프로그래머는 스스로 문법적으로 엄격하게 작성하는 것이 버그 없는 프로그램을 만들기 위해 좋습니다. 제가야구를 좋아하는데, 묘기를 잘 부리는 야구 선수는 결코 야구를 잘하는 선수가 아닙니다. 모든 것을 쉽고, 그리고 엄격하게 플레이하는 선수가 진짜 야구 잘하는 선수입니다.

프로그래밍도 이와 같습니다.

그래서 c = i; 보다는 c = static_cast(i); 가 더 바람직합니다.

이제 묵시적 캐스트(implicit cast)와 정적 캐스트(static cast)의 ‘허용’ 관점에 대해 이야기 해 보겠습니다.‘컴파일러에 의한 값 변환’ 을 위해 컴파일러가 기계어 코드를 생성하기전에 컴파일러는 캐스트 상황이 안전한 지에 대해 판단하고,안전 하다면 이를 허용하게 됩니다.


어떤 변수가 값을 가지고 있다면 그 변수에 어떤 값이 들어 있던 그것은 항상 안전 합니다. 그러나 만약 어떤 변수가 포인터를 가지고, 그 포인터가 가리키는 메모리가 유효하지 않다면 그것은 안전하지 않습니다.

그렇기 때문에 컴파일러가 캐스트를 허용하기 위하여 안전성을 검사하는 기준은 포인터 변수에 한해서 수행합니다. 아래의 코드를 보겠습니다.


char c = 'A';

char* pc = &c;

int* pi = pc;


위의 코드에서 int* pi = pc; 는 컴파일러에 의해 허용되지 않은 문장으로 오류를 발생시킵니다. pi 가 가리키는메모리는 실제 문자 변수이고, 만약 pi 를 이용한다면 4 byte 액세스(정수 타입이므로)가 이루어져 다운될 가능성이있습니다.

이것을 사전에 막고자 컴파일러는 이 문장을 허용하지 않습니다. 결론적으로 묵시적 캐스트(implicit cast)와 정적 캐스트(static cast)는 포인터 타입 캐스트에 대해 동일 타입인 경우에 한하여 허용합니다.

앞에서 이야기 한 것과 같이 대부분의 경우 정적 캐스트(static cast) 와 묵시적 캐스트(implicit cast)는 대부분 동일합니다. 다른 점은 없을까요?

class CBase { public: int a; };

class CDerived : public CBase { public: int b; };


위와 같이 클래스가 선언 되어 있을 때, 다음 코드를 보겠습니다.


CDerived* pDerived = new CDerived; CBase* pBase;
pBase = pDerived;


위의 pBase = pDerived; 이 허용 될까요? 포인터 타입의 캐스트 이고, 두 타입(pBase 와 pDerived)이 다른 타입이므로 허용되지 않아야 하겠지만 허용됩니다.

클래스 포인터에 대해 ‘is a’ 관계가 성립 한다면 묵시적 캐스트(implicit cast)는 허용됩니다. 왜냐하면 ‘is a’가 성립하는 두 타입 이라면 논리적으로도 하위 클래스가 상위 타입 데이터를 최소한 포함하기 때문에 100 % 안전하기때문입니다.


물론 정적 캐스트(static cast)도 똑 같습니다.

즉 pBase = static_cast(pDerived); 도 허용됩니다.

계속 묵시적 캐스트(implicit cast) 와 정적 캐스트(static cast)의 같은 점만을 이야기 하고 있죠?

하나 다른 것이 있습니다.

위의 클래스 선언을 그대로 포함한 상태에서 다음 코드를 보겠습니다.


CBase* pBase = new CDerived; CDerived* pDerived;

pDerived = pBase;


위의 pDerived = pBase; 이 허용 될까요? 허용 되지 않습니다.

왜냐하면 pDerived 가 실제가리키고 있는 것은 CBase 객체인데, 타입이 CDerived 으로 되었으므로 이를 이용하여 CDerived 전용 멤버인 b를 액세스 함으로서 다운될 가능성을 가지고 있기 때문입니다. 그러나 이 경우 정적 캐스트(static cast)는 가능 합니다.

즉 pDerived = static_cast(pBase); 는 가능 합니다.

이것 하나가 묵시적 캐스트(implicit cast) 와 정적 캐스트(static cast)의 차이점 입니다. 즉 클래스 포인터에대해 묵시적 캐스트(implicit cast)는 ‘is a’ 관계가 성립 하는 경우만 허용하고, 정적 캐스트(staticcast)는 ‘is a’ 뿐만 아니라 상속 관계만 성립한다면 항상 허용합니다.

물론 상속 관계조차 성립하지않는다면 정적 캐스트(static cast)도 캐스트를 허용하지 않습니다. 다음에는 reinterpret_cast 와const_cast 에 대해 살펴 보겠습니다. 오타 있으면 지적해 주시면 감사하겠습니다.

2. reinterpret_cast

reinterpret_cast 는 연관성이 없는 포인터 타입을 변환하기 위해서 사용 됩니다.

이것은 static_cast 와 비교해 생각해 보는 것이 이해하기 쉽습니다. 앞에서 static_cast 와 묵시적 캐스트(implicit cast)는 연관성 있는 데이터 타입에 대해서 캐스트가 가능하다고 했습니다.

즉 연관성이 없는 데이터에 대해서는 static_cast 와 묵시적 캐스트(implicit cast)를 사용할 수 없습니다.

char c = 'A';

char* pc = &c;

int* pi = pc; // int* pi = static_cast(pc); (X)


위 코드의 경우 int* pi = pc; 은 문법적으로 허용되지 않습니다. pc 와 pi 는 연관성이 없기 때문입니다.

그러나 이 경우 int* pi = reinterpret_cast(pc); 를 사용 한다면 캐스트(cast)가 될 것입니다. 앞의 예 외에 reinterpret_cast 는 클래스 타입에 대해 클래스가 연관 관계가 없는 경우에도 캐스트(cast)를 할 수 있습니다.

물론 이것이 캐스트(cast) 된다고 바람직 한 것이 아닙니다. 논리적으로 안전하지 않을 수 있기 때문입니다.

int* pi = reinterpret_cast(pc); 가 문법적으로 허용 된다고 하지만 pi 를 이용하여 메모리에 접근 한다면 프로그램이 다운될 수도 있기 때문입니다.

reinterpret_cast 는 C/C++ 개발자가 일반적으로 사용 했던 () 연산자를 이용한 캐스트와 대부분 같습니다. () 는 모든 경우에 강제 캐스팅을 수행 합니다.

물론 논리적으로 안전하지는 않는 경우가 있을 수 있지만,,,


reinterpret_cast 도 강제 캐스팅을 수행합니다.

reinterpret_cast 와 () 의 차이는reinterpret_cast 는 포인터 타입에 대한 캐스트만 수행할 수 있다는 것과 const 타입과 volite 타입의포인터 타입에 대해서는 캐스트를 수행할 수 없다는 것입니다.


int i = 65;

char c = reinterpret_cast (i);


물론 위와 같이 코딩하실 분은 없으시겠지만 위의 코드에서 reinterpret_cast 는 허용되지 않습니다. 저의 프로그래밍 철학으로 볼 때, 저는 이 reinterpret_cast 의 사용을 권장 하지 않습니다.

저는 항상 문법적 허용과 논리적 허용이 일치해야 한다고 생각합니다. 그러나 reinterpret_cast 는 문법적 허용과 논리적 허용이 일치 하지 않는 경우도 있습니다.

물론 () 연산자를 이용한 캐스트도 마찬가지 입니다. 다음에는 dynamic_cast 와 const_cast 에 대해 살펴 보겠습니다. 오타 있으면 지적해 주시면 감사하겠습니다.



3. const_cast

const_cast 는 포인터 타입과 참조형에 대해서만 사용됩니다. 그리고 동일 타입의 포인터와 참조형에 대해서만 사용됩니다.

동일 타입 외에 어떤 연관 관계 타입(is a 타입 포함)에 대해서도 허용되지 않습니다. 그렇다면 const_cast 의 사용 목적은 무엇일까요?


const CSample* pSample = new CSample;

CSample* pSample1 = pSample; pSample1->m = 10;


위의 코드에서 처럼 상수 목적으로 생성된 CSample 객체가 필요에 의해 변수 목적(pSample1->m = 10)으로 사용되어야 한다면 이것을 일단 변수 객체 포인터로 변환해야 합니다.

그러나 이 상황(CSample* pSample1 = pSample)에서 묵시적 캐스트(implicit cast)나 static_cast 또는 reinterpret_cast 를 사용한다면 컴파일러는 이를 허용하지 않을 것입니다.

이 때 사용할 수 있는 것이 const_cast 입니다.

즉 위의 코드의 경우 다음과 같이 사용해야 합니다.


CSample* pSample1 = const_cast(pSample);


const_cast 는 보시는 분에 따라 명시적 캐스트 연산자인 () 도 있는데, 왜 const_cast 가 필요한지 의문을 갖는 분도 있을 것입니다. 이것 역시 문법적인 엄격함 때문에 사용됩니다.

저는 사실 const_cast 사용을 권장하지 않습니다. 모든 변수는 선언 시에 목적을 가지고 선언 되어야 하는데, 만약 변수목적으로 사용될 가능성이 있었다면 애초에 상수로 선언되어서는 안됩니다. 프로그램 버그는 목적을 가지지않고 선언된 변수나 목적에어긋나게 사용되는 변수에 의해 발생되는 경우가 많습니다.


C++ 언어에서 많이 사용되지 않는 한정자 중에 volatile 타입 한정자가 있습니다. const_cast 는 volatile 타입 객체를 일반 타입 포인터나 참조형으로 변환하기 위해서도 사용됩니다.


volatile BOOL g_bCalc = FALSE; :

BOOL* p = const_cast(&g_bCalc);


물론 이 경우도 저는 권장하고 싶지 않습니다.


4. dynamic_cast


dynamic_cast 는 상당히 능동적인 캐스트 입니다. 다른 캐스트와 달리 dynamic_cast 는 실행 중에캐스트가 이루어 집니다. 실행 중에 캐스트의 대상이 되는 데이터를 능동적으로 판단하여 실행 코드가 캐스트를 수행합니다.

dynamic_cast 의 문법적 형식은 다음과 같습니다.


dynamic_cast <타입>(표현식)


표현식은 가상함수와 RTTI(Runtime Type Information)를 포함하는 클래스에 대한 포인터나 참조형, 혹은객체 일 수 있습니다. 그리고 타입은 역시 가상함수와 RTTI 를 포함하는 클래스의 포인터나 참조형일 수 있습니다.

타입에 한가지 예외적으로 void* 를 명시할 수도 있습니다. 표현식과 타입의 형식만 맞다면 컴파일러는 컴파일을 허용합니다.표현식과 타입이 연관 관계가 있던 없던 가상함수와 RTTI 를 포함하기만 하면 dynamic_cast 는 무조건 컴파일을허용합니다. 다른 캐스트와 달리 dynamic_cast 는 컴파일러의 허용 관점 보다는 실행 시 허용관점이 더 중요합니다.

우리도 실행 시 허용에 관심을 두어야 합니다.

RTTI(Runtime Type Information)란 클래스 타입 정보를 가지고 있는 클래스(type_info) 입니다. RTTI 는 컴파일러가 내부적으로 사용하는 정보 클래스로 아래와 같이 정의 되어있습니다.


class type_info {

   public: virtual ~type_info();

   int operator==(const type_info& rhs) const;

   int operator!=(const type_info& rhs) const;

   int before(const type_info& rhs) const;

   const char* name() const;

   const char* raw_name() const;

   private: void *_m_data;

   char _m_d_name[1];

   type_info(const type_info& rhs);

   type_info& operator=(const type_info& rhs);

};


type_info 클래스의 _m_data 멤버 변수는 이 글을 쓰는 저도 이해하지 못하며 NULL 로 설정되어 있습니다. 다음_m_d_name 멤버 변수는 실제 char 하나로 구성되어 있지만 클래스 이름을 가지고 있는 배열로서 type_info 가컴파일러에 의해 만들어 질 때 클래스 이름을 문자열로 갖도록 확장되어 생성 됩니다.

RTTI(Runtime Type Information) 를 만들기 위해 우리가 해야 할 일은 /GR 컴파일 옵션을 지정 하기만 하면 됩니다.


Visual C++ 6.0 같은 경우 [Project]-[Setting] 메뉴를 선택하여 [ProjectSettings] 대화상자를 나타낸 후, [C/C++] 탭의 [C++ Language] 카테고리 컴파일 옵션을 선택하고,[Enable Run-Time Type Information (RTTI)] 를 선택하면 됩니다. 앞의 옵션이 선택된 경우컴파일러는 RTTI 를 컴파일 하는 모든 클래스에 대해 생성합니다.

그러나 실제 각 클래스가 RTTI 를 사용하기위해서는 각 클래스는 최소한 하나 이상의 가상함수를 가져야 합니다. 만약 클래스가 가상함수를 하나도 가지지 않는다면 해당클래스는 타입으로는 RTTI 를 이용할 수 있지만 객체로는 RTTI 를 이용할 수 없습니다.

우리가 지금 알아보고있는 dynamic_cast 도 객체의 RTTI 를 이용한 동적(실행시) 캐스트 이므로 dynamic_cast 를 위해서도 해당클래스는 최소한 하나 이상의 가상함수를 가져야 합니다. 컴파일러는 RTTI 와 객체를 연결하기 위해서 가상함수 포인터 테이블을이용합니다. 이것은 변칙입니다.

언제부터 RTTI 가 C++ 언어에 도입 되었는지 알 수 없지만, 원래 C++언어의 가상함수 포인터 테이블은 순수한 가상함수에 대한 함수 포인터 배열입니다. RTTI 와 객체의 연결을 위해 C++ 언어는가상함수 포인터 테이블 앞에 4 byte 를 만들고 이것을 RTTI 와의 연결 고리로 사용하고 있습니다.

class CBase {

public: int m_base;

virtual void fun1 (void);

virtual void fun2 (void);

};


void CBase::fun1 (void) {

cout << "Base fun1" << endl;

}


void CBase::fun2 (void) {

cout << "Base fun2" << endl;

}


class CDerived : public CBase {

public: int m_derived; virtual void fun3 (void);

};


int main(int argc, char* argv[]) {

CBase* p1 = new CBase;

CDerived* p2 = new CDerived;

CDerived* p3 = dynamic_cast(p1);

CBase* p4 = dynamic_cast(p2);

return 0;

}


RTTI 가 생성된 경우 객체와 가상함수 포인터 테이블 모양이 조금 복잡해 집니다.

객체(p1, p2)는 여전히 가상함수 포인터 테이블을 가리키고 있지만 가상함수 포인터 테이블 앞에 숨겨진 4 byte 가 컴파일러에 의해 추가로 만들어 집니다.

그리고 바로 이 숨겨진 4 byte 가 클래스의 RTTI 포인터 테이블과 객체의 연결 고리로 사용됩니다.


RTTI 포인터 테이블은 객체 자신의 RTTI 나 상위 클래스의 RTTI 를 가리키는 또 다른 연결 고리입니다. 여러단계에 걸쳐 상속된 경우 실제 모양은 위의 그림보다 복잡해 집니다. 위의 그림은 원리를 설명하기 위하여 간단히 그려본 것입니다.

프로그램이 dynamic_cast 를 이용하여 캐스트를 한 경우 실행 코드는 dynamic_cast 의 표현식에기술된 객체를 이용하여 RTTI 포인터 테이블을 검색하고, 만약 RTTI 포인터 테이블 상에 일치하는 RTTI 가 존재 한다면표현식에 기술된 객체의 타입을 변환하여 반환하고, RTTI 포인터 테이블 상에 일치하는 RTTI 가 존재 하지 않는다면dynamic_cast 는 NULL(0) 을 반환할 것 입니다.

앞의 코드의 경우 p3 는 NULL 을 반환하고 p4 는 유효한 값을 반환할 것입니다.

이와 같이 dynamic_cast 는 다른 캐스트와 달리 표현식의 실제 객체를 추적하여 캐스트를 수행하므로 상당히 능동적이캐스트라 할 수 있습니다. 잘 설계된 클래스를 바탕으로 어플리케이션이 개발 될 때 어플리케이션 상의 클래스 포인터 타입은 대부분상위의 몇 가지 타입으로 운영됩니다. 그러나 간혹 실제 객체의 클래스 타입에 따라 어떤 논리를 처리해야 하는 경우가 생기는데,이때 우리는 dynamic_cast 를 다음과 같이 이용할 수 있습니다.


if (dynamic_cast(p)) { : : }


위의 if 블록은 p 가 CDerived 타입 인 경우만 수행될 것입니다. dynamic_cast 만큼 일반적이지는 않지만 RTTI 가 사용되는 경우가 한가지 더 있습니다.

바로 typeid 연산자가 바로 그것입니다. typeid(표현식) typeid 는 표현식이 RTTI 를 가지고 있다면 그것의RTTI 클래스(type_info) 참조형을 반환합니다. RTTI 클래스(type_info) 참조형을 이용하여 클래스 이름이나비교를 수행할 수 있습니다.

typeid 를 사용할 때 주의할 것은 클래스 포인터가 전달된 경우 클래스 포인터 타입에 대해 별도의 RTTI 클래스 참조형이 반환된다는 것이다.


int main(int argc, char* argv[]) {

CBase* p1 = new CBase;

CDerived* p2 = new CDerived;

const type_info& t = typeid (p1);

const type_info& t1= typeid(*p1);

return 0;

}


위의 typeid(p1) 과 typeid(*p1) 은 서로 다른 type_info 가 반환됩니다. typeid(*p1) 은dynamic_cast 와 같이 p1 객체를 추적하여 RTTI 를 알아내고 그것의 type_info 참조형으로 반환합니다.그러나 typeid(p1) 은 p1 포인터 타입(CBase*)에 대해 RTTI 가 반환됩니다.

이때 반환되는 RTTI 는 객체와 어떠한 연결 고리도 가지지 않는 RTTI 입니다.

:

CRuntimeClass

Development/C / C++ 2006. 11. 20. 17:04
출처 : 검색 부분 발췌

헤더를 보면 다음과 같은 매크로가 있다.

   DECLARE_DYNCREATE(CMainFrame)

이것은 무엇인가?... 궁금하지 않은가?.. 바로 이것은.. RunTimeClass라는 구조체를 사용하겠다고 선언하는 것이다. 자세한 것은 솔직이 본인도 잘 모르지만.. 런타임 구조체라는게 어떤 것이라는 건 한번 되짚고 넘어가 볼 필요가 있다.

다음은 런타임 클래스의 정의를 설명해 놓은 원문이다.

CRuntimeClass does not have a base class.
Each class derived from CObject is associated with a CRuntimeClass structure that you can use to obtain information about an object or its base class at run time.

// 런타임 클래스 구조체는 기초 클래스가 없다.
CObject에서 파생된 각각의 클래스는 런타임 클래스와 연결되어 당신은 프로그램 실행시 (이하 실시간) 그때그때마다 오브젝트나 그것의 베이스 클래스에 대한 정보를 얻어낼 수 있다. //

이상이다. 이건 무슨 뜻일까?.. 프로그램이 실행될 때.. 탑다운 방식에서는 무조건 위에서 아래로 내려오면서 실행된다. 하지만 MFC는 적어도 그렇지 않다.
프로그램이 시작되면서 이벤트를 발생시키고 각각 그 이벤트의 정보가 다르다. 실시간마다.. 서로 다른 정보를 이쪽 저쪽으로 왔다갔다 하면서.. 설정한다는 말이다. 바로.. 프로그램이 긴박하게 돌아갈 때.. 우리가 발생시키는 메시지(이것 말고도 사실 실시간 중에는 많은 정보들이 왔다갔다 할 것이다. 하드웨어쪽으로 몰라서 뭐라고 자세히 설명드릴 수 없는 데 대해 사죄의 말씀을..--;)의 정보를 숨가쁘게 받아내야 할 것이고.. 런타임 에러에 걸리는지 안걸리는지... 실시간 에러처리까지 헤줘야 하는 것이다. 하드웨어에서 이처럼 그때그때 실시간 처리를 해주지 못한다면 그 프로그램의 실행은 불 보듯 뻔한거 아니겠는가.. 불.가.능..이겠다..

바로 런타임 구조체는 그런 면에서 유용하게 쓰인다. 나중에 Multi Document Interface를 공부하게 될 즈음.. View와 Document 그리고 Frame을 App에서 각각 실시간으로 어떻게 연결해주는지... 보게 될 것이다. 사실상... 런타임까지는 본인도 자세히 알지는 못하는 바다. MDI 만들 때 가끔씩 써보는 것 외엔..

이제 무슨 말인지 이해하셨을 것이다. 어쨌든.. 저 위의 한 줄.. 은 이러한 런타임 구조를 쓰겠다고 선언하는 것이다.


:

동적 생성 지원 클래스 만들기

Development/C / C++ 2006. 11. 20. 16:45
//RTTI(run time type information) example
#include
#include



#define RUNTIME_CLASS(class_name) (&class_name::class##class_name)



class CObject;

//{{struct CRuntimeClass------------------------------------------------
struct CRuntimeClass {
char m_lpszClassName[21];
int m_nObjectSize;
CObject* (*pfnCreateObject)();

CObject* CreateObject();
};//struct CRunTimeClass

CObject* CRuntimeClass::CreateObject() {
return (*pfnCreateObject)();//함수 포인터를 이용하여
//간접적으로 함수를 호출한다.
}//CRuntimeClass::CreateObject()
//}}struct CRuntimeClass------------------------------------------------

//{{class CObject-------------------------------------------------------
class CObject {
//CObject는 순수 가상함수를 포함하지 않으므로
//추상 클래스가 아니다. 하지만, 생성자가 protected로 설정되었으므로,
//CObject객체를 생성할 수 없다.
public:
virtual CRuntimeClass* GetRuntimeClass() const { return NULL; }
//파생 클래스에서 반드시 구현할 필요가 없으므로
//순수가상함수가 아니다.
static CRuntimeClass classCObject;
//DECLARE_DYNAMIC(CObject)
virtual ~CObject(){}
protected:
CObject(){ printf("CObject constructor\n"); }
};//class CObject

CRuntimeClass CObject::classCObject={//IMPLEMENT_DYNAMIC(CObject)
"CObject",sizeof(CObject),NULL
};
//}}class CObject-------------------------------------------------------

//{{class CAlpha--------------------------------------------------------
class CAlpha : public CObject {
public:
virtual CRuntimeClass* GetRuntimeClass() const {
return &classCAlpha;
}
static CRuntimeClass classCAlpha;
//DECLARE_DYNAMIC(CAlpha)
static CObject* CreateObject();
//DECLARE_DYNCREATE(CAlpha)
protected:
CAlpha() { printf("CAlpha constructor\n"); }
};//class CAlpha

CRuntimeClass CAlpha::classCAlpha = {//IMPLEMENT_DYNAMIC(CAlpha)
"CAlpha", sizeof(CAlpha), CAlpha::CreateObject
};

CObject* CAlpha::CreateObject() {//IMPLEMENT_DYNCREATE(CAlpha)
return new CAlpha;
}
//}}class CAlpha--------------------------------------------------------

//{{class CBeta---------------------------------------------------------
class CBeta : public CObject {
public:
virtual CRuntimeClass* GetRuntimeClass() const {
return &classCBeta;
}
static CRuntimeClass classCBeta;
//DECLARE_DYNAMIC(CBeta)
static CObject* CreateObject();
//DECLARE_DYNCREATE(CBeta)
protected:
CBeta() { printf("CBeta constructor\n"); }
};//class CBeta

CRuntimeClass CBeta::classCBeta = {//IMPLEMENT_DYNAMIC(CBeta)
"CBeta", sizeof(CBeta), CBeta::CreateObject
};

CObject* CBeta::CreateObject() {//IMPLEMENT_DYNCREATE(CBeta)
return new CBeta;
}
//}}class CBeta---------------------------------------------------------

void main() {
//Create CAlpha
CRuntimeClass* pRTCAlpha=RUNTIME_CLASS(CAlpha);
CObject* pObj1;

pObj1=pRTCAlpha->CreateObject();
//struct RuntimeClass의 CreateObject()가 호출되지만
//IMPLEMENT_DYNCREATE 매크로에 의해 CAlpha의 &CreateObject()가
//대입되어 있으므로, 결국은 CAlpha의 CreateObject()가 호출되어
//동적으로 객체를 생성하게 된다.
//문제를 푸는 열쇠는 바로 '함수 포인터'이다.
printf("CAlpha class=%s\n", pObj1->GetRuntimeClass()->m_lpszClassName);

//Create CBeta
CRuntimeClass* pRTCBeta=RUNTIME_CLASS(CBeta);
CObject* pObj2;

pObj2=pRTCBeta->CreateObject();
printf("CBeta class=%s\n", pObj2->GetRuntimeClass()->m_lpszClassName);
//일관된 방법으로 클래스를 만들고 해제하는 것에 주목하라.

delete pObj1;
delete pObj2;
}//main





// 매크로를 사용한 소스



//RTTI(run time type information) example
#include
#include



//{{RTTI macros---------------------------------------------------------
#define RUNTIME_CLASS(class_name) (&class_name::class##class_name)



#define DECLARE_DYNAMIC(class_name) static\
CRuntimeClass class##class_name;



#define IMPLEMENT_DYNAMIC(class_name) CRuntimeClass\
class_name::class##class_name = {\
(#class_name),\
sizeof(class_name),\
class_name::CreateObject };



#define DECLARE_DYNCREATE(class_name) static CObject* CreateObject();



#define IMPLEMENT_DYNCREATE(class_name) CObject*\
class_name::CreateObject() {\
return new class_name; }
//}}RTTI macros---------------------------------------------------------

class CObject;//forward declaration of CObject

//{{struct CRuntimeClass------------------------------------------------
struct CRuntimeClass {
char m_lpszClassName[21];
int m_nObjectSize;
CObject* (*pfnCreateObject)();

CObject* CreateObject();
};//struct CRunTimeClass

CObject* CRuntimeClass::CreateObject() {
return (*pfnCreateObject)();//함수 포인터를 이용하여
//간접적으로 함수를 호출한다.
}//CRuntimeClass::CreateObject()
//}}struct CRuntimeClass------------------------------------------------

//{{class CObject-------------------------------------------------------
class CObject {
//CObject는 순수 가상함수를 포함하지 않으므로
//추상 클래스가 아니다. 하지만, 생성자가 protected로 설정되었으므로,
//CObject객체를 생성할 수 없다.
public:
virtual CRuntimeClass* GetRuntimeClass() const { return NULL; }
//파생 클래스에서 반드시 구현할 필요가 없으므로
//순수가상함수가 아니다.
DECLARE_DYNAMIC(CObject)
//static CRuntimeClass classCObject;
virtual ~CObject(){}
protected:
CObject(){ printf("CObject constructor\n"); }
};//class CObject

CRuntimeClass CObject::classCObject={
"CObject",sizeof(CObject),NULL
};
//}}class CObject-------------------------------------------------------

//{{class CAlpha--------------------------------------------------------
class CAlpha : public CObject {
public:
virtual CRuntimeClass* GetRuntimeClass() const {
return &classCAlpha;
}
DECLARE_DYNAMIC(CAlpha)
//static CRuntimeClass classCAlpha;
DECLARE_DYNCREATE(CAlpha)
//static CObject* CreateObject();
protected:
CAlpha() { printf("CAlpha constructor\n"); }
};//class CAlpha

IMPLEMENT_DYNAMIC(CAlpha)
//CRuntimeClass CAlpha::classCAlpha = {
// "CAlpha", sizeof(CAlpha), CAlpha::CreateObject
//};

IMPLEMENT_DYNCREATE(CAlpha)
//CObject* CAlpha::CreateObject() {
// return new CAlpha;
//}
//}}class CAlpha--------------------------------------------------------

//{{class CBeta---------------------------------------------------------
class CBeta : public CObject {
public:
virtual CRuntimeClass* GetRuntimeClass() const {
return &classCBeta;
}
DECLARE_DYNAMIC(CBeta)
//static CRuntimeClass classCBeta;
DECLARE_DYNCREATE(CBeta)
//static CObject* CreateObject();
protected:
CBeta() { printf("CBeta constructor\n"); }
};//class CBeta

IMPLEMENT_DYNAMIC(CBeta)
//CRuntimeClass CBeta::classCBeta = {
// "CBeta", sizeof(CBeta), CBeta::CreateObject
//};

IMPLEMENT_DYNCREATE(CBeta)
//CObject* CBeta::CreateObject() {
// return new CBeta;
//}
//}}class CBeta---------------------------------------------------------

void main() {
//Create CAlpha
CRuntimeClass* pRTCAlpha=RUNTIME_CLASS(CAlpha);
CObject* pObj1;

pObj1=pRTCAlpha->CreateObject();
//struct RuntimeClass의 CreateObject()가 호출되지만
//IMPLEMENT_DYNCREATE 매크로에 의해 CAlpha의 &OnCreate()가
//대입되어 있으므로, 결국은 CAlpha의 OnCreate()가 호출되어
//동적으로 객체를 생성하게 된다.
//문제를 푸는 열쇠는 바로 '함수 포인터'이다.
printf("CAlpha class=%s\n", pObj1->GetRuntimeClass()->m_lpszClassName);

//Create CBeta
CRuntimeClass* pRTCBeta=RUNTIME_CLASS(CBeta);
CObject* pObj2;

pObj2=pRTCBeta->CreateObject();
printf("CBeta class=%s\n", pObj2->GetRuntimeClass()->m_lpszClassName);
//일관된 방법으로 클래스를 만들고 해제하는 것에 주목하라.

delete pObj1;//delete pRTCAlpha는 왜 (논리적) 에러인가?
delete pObj2;
}//main

: