나는 여태까지 C++ 은 정적 컴파일이다보니

동적으로 뭔가를 한다는 게 불가능할 줄 알았는데,


그냥 내가 무식했나보다...ㅎㅎㅎ..



C++ 에서 4 가지 캐스팅 연산자 (dynamic_cast, static_cast, const_cast, reinterpret_cast) 가운데

dynamic_cast 에 대해 알아보고자 한다.



 dynamic_cast 상속 관계 안에서 포인터나 참조자의 타입


기본 클래스 → 파생 클래스 로의 다운캐스팅


다중 상속에서 기본 클래스 간의 안전한 타입 캐스팅에 사용된다.

(안전한 타입 캐스팅 : 런타임에 타입 검사를 하겠다는 의미)


const_cast 와 같이 용도가 명확하기 때문에 다른 용도로는 사용하지 못한다.



+추가로

객체가 위치한 메모리의 시작부분을 찾는 데도 사용된다고 한다.

아래와 같이 p 에 시작주소가 담겨진다.

void* p = dynamic_cast<void*> (ObjectPointer);



* dynamic_cast 의 제약사항

1) 상속 관계 안에서만 사용할 수 있다.

2) 하나 이상의 가상 함수를 가져야 한다.

3) 컴파일러의 RTTI (Runtime Type Information) 설정이 켜져있어야 한다.



[예제1]

class Base {
public:
    virtual void print() { cout << "This is Base" << endl; }
};

class Derived : public Base {
public:
    void print() { cout << "This is Derived" << endl; }
};

void main()
{
    Base* pBase1 = new Base;
    Base* pDerived1 = new Derived;

    Derived* pDerived2 = new Derived;
    Derived* pDerived = nullptr;

    // 컴파일 실패 : 부모는 자식이 될 수 없음
    pDerived = pBase;

    // 컴파일 성공 : but 부모는 자식이 될 수 없으므로 널 포인터를 리턴한다!
    pDerived = dynamic_cast<Derived*> (pBase);
    if ( pDerived == nullptr )
        cout << "failed to casting pBase->pDerived" << endl;


    // 컴파일 실패 : pDerived1 의 타입이 Base* 이므로, 부모는 자식이 될 수 없음
    pDerived = pDerived1

    // 컴파일 성공 : 런타임에 타입 변환이 성공하여 Derived 타입의 포인터를 리턴한다!
    pDerived = dynamic_cast<Derived*> (pDerived1);
    if ( pDerived )
        pDerived->print();


    // 컴파일 성공 : 이런 경우에는 캐스팅이 필요없다.
    pDervied = pDerived2;
}


위 예제에서의 키포인트는 

'포인터가 실제로 가르키는 대상이 캐스팅이 될 수 있는 녀석인가' 이다.



dynamic_cast 는 캐스팅에 실패할 경우,

대상이 pointer 면 nullptr 를 반환하고

대상이 참조자이면 bad_cast 예외를 던진다.


이 점이 위에서 언급한 안전한 타입 캐스팅의 의미이다.

캐스팅의 가능 여부를 런타임에 검사하여 처리할 수있다.




다음은 다중 상속일 때, 기본 클래스 간의 타입 캐스팅(Cross-Casting)을 다룬다.


[예제2]

class BaseOne {
public:
    virtual void print() { cout << "This is BaseOne" << endl; }
};

class BaseTwo {
public:
    virtual void print() { cout << "This is BaseTwo" << endl; }
};

class Derived : public BaseOne, public BaseTwo {
public:
    void print() { cout << "This is Derived" << endl; }
};

void main()
{
    BaseOne* pBaseOne = nullptr;
    BaseTwo* pBaseTwo = new Derived;

    // 컴파일 실패 : BaseOne 과 BaseTwo 타입은 호환 불가
    pBaseOne = pBaseTwo;

    // 컴파일 성공 : 기본 클래스 간의 타입 캐스팅 가능
    pBaseOne = dynamic_cast<BaseOne*> (pBaseTwo);

    if ( pBaseOne )
        pBaseOne->print();
}


[예제2] 를 정리하자면 아래의 그림과 같다.



크로스 캐스팅의 한 사용 예로는 

각 기본 클래스 포인터(or 참조자) 타입의 컨테이너 혹은 배열을 운용하면서 

서로 간의 요소를 교환할 때 사용할 수 있다.




여기까지 dynamic_cast 의 용도를 알아보았다.

( [예제1] : 다운 캐스팅[예제2] : 크로스 캐스팅 )



참고로 다중 상속은 권장하지 않는 분위기(?)라서 설계를 재검토 하도록 하자.




※ 포스팅 참고 자료:

http://prostars.net/55

http://msdn.microsoft.com/ko-kr/library/cby9kycs.aspx



포스팅의 엄청난 도움을 주신 (+게으른 저를 포스팅하게 만들어 주셨습니다)

http://prostars.net/55 님께 감사의 인사를 덧붙입니다. (__)


by kelicia 2014. 10. 26. 23:12

중복 컴파일 방지를 위해 자주 쓰이는 전처리기 지시자들에 대해 비교해 보고자 한다.



1) #ifndef


먼저 다음과 같은 코드가 있다고 하자.


First.h

#ifndef _FIRST
#define _FIRST

class First
{
};
#endif

Second.h

#ifndef _SECOND
#define _SECOND

#include "First.h"

class Second
{
};
#endif

Main.cpp

#include "First.h"
#include "Second.h"

void Main()
{
}


Main.cpp 를 컴파일 해보면 #include 의 작동방식에 따라

그 파일의 코드가 그대로 복사될 것이다.


#ifndef _FIRST
#define _FIRST

class First
{
};
#endif

////////////////////////////////

#ifndef _SECOND
#define _SECOND

#include "First.h"

class Second
{
};
#endif

////////////////////////////////

void Main()
{
}


위와 같이 되고 Second 에서 또 한번 #include "First.h" 를 읽게 될 것이다.

그 결과로 ,


#ifndef _FIRST
#define _FIRST

class First
{
};
#endif

////////////////////////////////

#ifndef _SECOND
#define _SECOND

#ifndef _FIRST
#define _FIRST

class First
{
};
#endif

class Second
{
};
#endif

////////////////////////////////

void Main()
{
}


위의 코드와 같이 Main.cpp 에는 First.h 이 2번 복사되는 결과를 볼 수 있다.


결론을 내리자면, #ifndef 의 방식으로 중복 컴파일을 피하는 것은

#include 명령어를 사용한 횟수 만큼 전처리기가 그 파일을 복사해오고,

복사해 온 코드 상단에 작성된 #ifndef 구문을 읽고 컴파일 여부를 확인 한다는 것이다.


이렇게 결론을 지으면 

'그런데도 왜 이 방식을 써서 중복 컴파일 방지를 하냐 ?!'

라고 할 수 있다. 물론 장점도 존재하고 포스팅 마무리할 때쯤 언급하겠다.




2) #pragma once


그런데 나는 - pragma - 라는 단어가 대체 무슨 뜻인지 궁금해서 찾아보았다.



짜증. 괜히 맛있는 치킨만 먹고 싶어졌다.

그냥 컴파일러 지시자? 정도로만 이해해야겠다.


구글에 검색해도 pragma 가 뭐 하는 녀석인지만 나와있지,

정작 pragma 라는 단어 자체의 뜻에 대해서는 잘 안 나오는 듯_-_



어쨌든 잡소리였고 다시 중복 컴파일 방지를 위한 #pragma once 명령어로 돌아가자.


이 지시자가 의미하는 바는 '1번만 컴파일 하겠다' 라는 뜻으로 알고 있다.

msdn 에 따르면 (http://msdn.microsoft.com/en-us/library/4141z1cx(v=vs.80).aspx)


Pragma 지시자의 토큰 값으로 들어갈 수 있는 once 키워드 : 
Specifies that the file will be included (opened) only once by the compiler when compiling a source code file.

Remark : This can reduce build times as the compiler will not open and read the file after the first #include of the module.


아까 말한 것과 다를 바 없고, 이게 전부다. 

#ifndef 과 다르게 include 로 인해 여러 번 파일을 열어서 읽는 것이 아니라서 빌드 타임을 감소시킬 수 있다는 점.




3) 정리


#ifndef

- 앞서 보았듯이 헤더 파일을 여러 번 include 하게 되면 define 여부를 계속 체크해야 되기 때문에

컴파일 단계 중에서 파일 해석 단계의 속도가 #pragma once 에 비해서 다소 느리다고 할 수 있다.

하지만 장점을 꼽자면 전처리기 지시자(Preprocessor directive)라서 모든 컴파일러에서 동작한다는 점이다.


#pragma once :

- 1번만 컴파일 하고 그 뒤로부터는 동일한 파일의 경우, 읽기조차 하지 않는다. 그래서 파일 해석 단계의 속도가

#ifndef 보다는 빠르다. 하지만 아까 사전적 의미를 찾아봤을 때, 자세히 보면 'A compiler directive' 라고 적혀있다.

컴파일러 지시자로 특정 컴파일러에서만 동작하는 지시자이며 Visual C++ 5.0 이상에서만 동작한다고 한다.



얼핏 속도면에서 보면 #pragma once 가 좋아보일 수 있으나 

거대 프로젝트가 아닌 이상 속도에서 큰 차이를 볼 수 있을까? 하는 의문이 든다.


그리고 안정성(호환성)과 범용성을 고려한다면 #ifndef 방식을 택해야 할 것이다.




4) Reference


http://ace01kr.tistory.com/entry/%ED%97%A4%EB%8D%94%ED%8C%8C%EC%9D%BC-%EC%A4%91%EB%B3%B5%EB%B0%A9%EC%A7%80-pragma-once-vs-ifndefendif


http://neodreamer-dev.tistory.com/310


http://msdn.microsoft.com/en-us/library/t22e924w(v=vs.80).aspx


'Programming > C++' 카테고리의 다른 글

dynamic_cast 연산자  (0) 2014.10.26
정적 바인딩(Static binding) vs. 동적 바인딩(Dynamic binding)  (0) 2014.08.19
by kelicia 2014. 8. 25. 19:21


* Binding 

- 프로그램 구성 요소의 성격을 결정해주는 것

ex ) 변수의 데이터 타입이 무엇인지 정해지는 것




 종류

정적 바인딩(Static binding)

동적 바인딩(Dynamic binding)

 정의

 컴파일 시간에 성격이 결정되는 것

 실행 시간(runtime)에 성격이 결정되는 것

 예시

C언어 컴파일 시간에 변수의 데이터 타입이 결정

Python(Interpreter 언어) 런타임에 값에 따라 

 변수의 데이터 타입이 결정

 장단점

 컴파일 시간에 많은 정보가 결정되므로 실행 효율↑

 런타임에 자유롭게 성격이 바뀌므로 적응성↑




* 함수의 바인딩

- 함수를 만들어 컴파일을 하면 각각의 코드가 메모리 어딘가에 저장된다.

그리고 함수를 호출하는 부분에는 그 함수가 저장된 메모리 번지수(주소값)이 저장된다.


프로그램 실행 → 함수 호출 → 함수가 저장된 주소로 점프 → 함수 실행 → 원래 위치


위 과정에서 함수를 호출하는 부분에 함수가 위치한 메모리 번지로 연결시켜 주는 것을 바인딩(Binding) 이라고 한다.



- 함수를 바인딩하는 2가지 방법

(1) 정적 바인딩 (일반 함수)

컴파일 시간에 호출될 함수로 점프할 주소가 결정되어 바인딩 되는 것.

(2) 동적 바인딩 (가상 함수)

실행 파일을 만들 때 바인딩 되지 않고 보류 상태 둔다.

점프할 메모리 번지를 저장하기 위한 메모리 공간(4 byte)을 가지고 있다가 런타임에 결정.

=> 단점 : 타입 체킹으로 인한 수행 속도 저하 / 메모리 공간 낭비

=> 가급적 정적 바인딩 사용


?? 2 가지의 단점이 있음에도 불구하고 동적 바인딩을 하는 이유 ??

- 어떤 포인터에 의해 접근되었는 지에 상관없이 참조된 인스턴스의 실제 클래스형에 따라 재정의된 함수 호출이 가능!




이 포스팅의 원본 출처이자 C++ 에서

Template, 정적 바인딩 vs. Virtual Function, 동적 바인딩 으로 실험한 결과를 볼 수 있는 블로그 :

http://blog.daum.net/sox25/2


위 출처에서 정적 바인딩이 항상 동적 바인딩 보다 빠르다고 할 수 없다는 결과를 볼 수 있다.





* 프로그래밍 언어에서의 2가지 Type System


(1) 정적 타입 (Static Type)

- 컴파일 시에 타입이 결정

- 변수를 선언할 때, 반드시 앞에 타입을 명시해야 하는 언어들은 모두 정적 타입 시스템에 속한다.

ex ) C, C++, Java ...


- 결국 장점에 대해 언급을 하자면,

. 컴파일 시에 타입에 대한 정보를 결정하기 때문에 속도↑ (효율성↑)

타입 에러로 인한 문제점을 초기에 발견할 수 있어 타입의 안정성↑


(2) 동적 타입 (Dynamic Type)

- 런타임 시에 타입이 결정

- 코드를 작성할 때, 변수 타입을 명시하지 않고 런타임에 변수의 값에 따라 타입이 결정되는 시스템.

ex ) Python, Ruby, SmallTalk ...


- 장점 : 유연성 (혹은 적응성)

런타임까지 타입에 대한 결정을 끌고 갈 수 있기 때문에 많은 선택의 여지가 있다.

- 단점 : 안정성

인터프리터 언어는 배우기는 쉬우나 실행 도중에 변수에 예상치 못한 타입이 들어와 Type Error 를 뿜는 경우가 생긴다.

그러니 너무 편리함에만 의존하면 안정성 저하를 유발할 수 있다.




* Type System 에 따른 상속의 의미 차이


객체지향에서 중요한 개념 중에 하나가 다형성(Polymorphism) 이고,

일반적으로 다형성은 함수의 오버라이딩, 즉 동적 바인딩에 의한 것이다.


(1) 정적 타입(Static Type) 의 상속

: 코드 재사용 + 타입의 호환성 유지 를 목적으로 한다.

- 컴파일 시에 상속의 관계를 파악하여 상속관계에 있는 객체들 간의 타입 호환성을 유지해

타입은 컴파일 시에 결정되나 메소드 호출 시에 값에 따라 그 객체의 메소드를 호출하게 된다.

이와 같은 방법으로 동적 바인딩에 대한 문제를 해결했다고 할 수 있다.


(2) 동적 타입(Dynamic Type) 의 상속

: 코드 재사용 을 목적으로 한다.

- 타입 속성에 따라 메소드의 형태만 같으면 동적 바인딩을 유도할 수 있다.



간단하게 예시를 보면,

a.call();


정적 타입의 경우 :

컴파일 시에 a 객체의 타입에 호환될 수 있는 타입을 결정하고, 

런타임 시에 객체의 값에 따른 호환성 있는 객체의 메소드를 호출한다.

=> 즉, 상위 클래스의 코드 재사용과 동시에 타입의 호환성을 유지하는 목적을 가진다.


동적 타입의 경우 :

동적 타입 시스템에 의해 동적 바인딩이 자동적으로 유도되어 단지 상위 클래스의 코드를 상속받는 의미가 전부다.




바인딩이 일어나는 타이밍에 관련 포스팅 :

http://destiny738.tistory.com/178



휴.. 팔수록 어렵다 ㅠ_ㅠ


'Programming > C++' 카테고리의 다른 글

dynamic_cast 연산자  (0) 2014.10.26
.h 컴파일 : #ifndef vs. #pragma once  (0) 2014.08.25
by kelicia 2014. 8. 19. 11:25
| 1 |