기본 콘텐츠로 건너뛰기

[C++] Cast에 대해서...

출처 AiWisdom님의 블로그 | 갱주니
원문 http://blog.naver.com/process3/20017834722
캐스트 연산은 주어진 식이 가지고 있는 형을 다른 형으로 강제로 바꾸는것입니다.

C++에는 (C 시절부터 존재하는 C 스타일 캐스트를 제외하고) 다음 네 가지 종류의 캐스트 연산이 있습니다.
  1. dynamic_cast
  2. static_cast
  3. reinterpret_cast
  4. const_cast

  1. dynamic_cast
  2. dynamic_cast(e)는 부모 클래스와 자식 클래스의 관계에 있는 포인터 형 사이의 변환 또는 레퍼런스 형 사이의 변환을 수행하는데,
    • 같은 형 사이의 변환
    • 널 포인터의 변환
    • 자식 클래스로부터 부모 클래스로의 변환
    과 같은 '뻔한' 경우가 아니라면 e는 다형적 형(polymorphic type; 가상 함수가 포함된 클래스 형)의 좌변값이나 포인터여야 하며,
    컴파일시에 변환이 이루어지는 다른 종류의 캐스트 연산과는 달리 실행시에 동적 형(dynamic type)에 근거한 변환이 시도되고, 변환의 성공 여부를 검사하는 의미도 함께 가지고 있습니다.

    포인터의 경우 변환이 실패하면 결과값은 널 포인터가 되는데, 이를 if 등의 조건 검사에 활용할 수 있습니다.
    struct animal { virtual void ~animal(); };
    struct dog : animal { void bark(); };
    struct cat : animal { void mew(); };
      
    void test(animal* a)
    {
       if (dog* d = dynamic_cast(a)) d->bark(); else
       if (cat* c = dynamic_cast(a)) c->mew();
    }
    
    레퍼런스의 경우 변환이 실패하면 헤더에 정의되어 있는 std::bad_cast 예외가 발생합니다. 즉 이는 주어진 변환이 성공할 것을 알고 있을 때 주로 사용합니다.

    void test(animal& a)
    {
        dog& d = dynamic_cast(a);
        d.bark();
    }
    
    dog d; test(d); // 성공
    cat c; test(c); // 실패 - std::bad_cast 예외 발생
    
    dynamic_cast와 비슷한 성질을 가지고 있으면서 변환 대신 형 검사만 하는 typeid 연산자도 있는데,
    피연산자는 식이나 형이 되고, 연산의 결과값은 헤더에 정의되어 있는 std::type_info 형의 좌변값입니다.
    void test_equal(animal& x, animal& y)
    {
        if (typeid(x) == typeid(y)) { /* 같은 종류 */ }
        else { /* 다른 종류 */ }
    }
    
    void test_dog(animal& x)
    {
        if (typeid(x) == typeid(dog)) { /* x is a dog */ }
    }
    
    dynamic_cast와 typeid는 C++에서 제공하는 실행시의 형 정보(RTTI; run-time type information)의 일환인데,
    이를 남발하면 클래스 체계를 확장하고 관리하기가 어려워지므로 꼭 필요한 경우에만 사용하고 되도록 가상 함수를 통한 다형성을 이용하는 것이 바람직합니다.

  3. static_cast
  4. static_cast(e)는 가장 일반적인 형태의 캐스트 연산으로,
    어떤 임시 변수 t를 T t(e);와 같이 선언하고 초기화하여 그 임시 변수 t의 값을 사용하는 것과 같은 암시적인 변환을 비롯하여,
    산술형(char, int, double 등) 및 열거형(enum) 사이의 변환, 부모 클래스와 자식 클래스의 관계가 관련된 변환,
    void 형으로의 변환 등을 수행할 수 있습니다.
    다만 부모 클래스와 자식 클래스 사이의 변환은 주어진 식의 동적 자료형(dynamic type)이 아닌 정적 자료형(static type)에 전적으로 의존합니다.
    inline int integer_quotient(double a, double b)
    { return static_cast(a / b); }
    
    animal* a = new dog;
    dog* d = static_cast(a); // 올바른 캐스트 연산
    
    animal* a = new cat;
    dog* d = static_cast(a); // 잘못된 캐스트 연산
    
    animal* a = new animal;
    dog* d = static_cast(a); // 잘못된 캐스트 연산
    

  5. reinterpret_cast
  6. reinterpret_cast(e)는 서로 다른 형의 포인터 사이의 변환이나, 정수와 포인터 사이의 변환 등 서로 관계가 없는 형 사이의 변환을, 구현체가 정의하는 방법에 따라 수행합니다.
    정수와 포인터 사이의 변환의 결과값은 주로 e를 표현하는 비트열을 그대로 정수 및 포인터로 해석한 값이 됩니다.

    reinterpret_cast는 그 변환 방법이 대부분 구현체가 정의하도록 맡겨져 있어서 이식성을 떨어뜨리며, 요구된 변환이 올바른 변환인지의 여부를 검사하지 않으므로 신중하게 사용해야 합니다.
    unsigned char* const video_base =
    reinterpret_cast(0x80000000);
    
    unsigned int ui = 0x01234567;
    *reinterpret_cast(&ui) = 0xFF;
    

  7. const_cast
  8. const_cast(e)는 const 또는 volatile으로 한정된 형에서 이들을 떼어내는 변환을 수행할 수 있습니다. 이는 C++ 형 체계를 무너뜨릴 수 있으므로 신중하게 사용해야 합니다.
    void lie(const int* pci)
    {
        int* pi = const_cast(pci);
        *pi = 0;
    }
    
    int i = 1;
    lie(&i); // OK
    
    const int ci = 1;
    lie(&ci); // Ouch!!
    

위 네 가지 중에서 dynamic_cast를 제외한 셋은 C에서 (type)expression 의 형태로 사용할 수 있었던 것인데, C++에서도 "C 스타일 캐스트 연산" 이라고 불리며 남아 있기는 합니다만,
새로 작성하는 C++ 코드에서는 C++ 스타일의 캐스트 연산을 사용하는 것이 좋습니다.
이는 어떤 종류의 변환을 프로그래머가 의도하는지 명확하게 나타내 주며, 위험할 수 있는 캐스트 연산이 코드에서 좀 더 두드러져 보이도록 하고 찾기도 쉽게 만들어주기 때문입니다.

위의 내용은 캐스트 연산에 대한 일반적인 설명인데, 좀 더 구체적인 상황에서의 적용 예를 보고 연습을 해 보시려면 GotW #17을 참조해 보시기 바랍니다.

출처 : http://funspace.org/

댓글

이 블로그의 인기 게시물

80040154 오류로 인해 CLSID가 {xxxx-...}인 구성 요소의 COM 클래스 팩터리를 검색하지 못했습니다.

원문보기 .NET 으로 만든 응용프로그램에서 com 객체를 호출한 경우 Windows7 64bit 에서 제목과 같은 에러가 발생했다. Win32 COM 과 .NET 프로그램간의 호환성 때문에 생긴 문제였다. 원인은 .NET 실행시 JIT 컴파일러에 의해 최적화된 기계어로 변환되기 때문.. Win32 COM은 컴파일시.. Win32 COM에 맞춰 빌드 속성에서 하위버전으로 맞춰 컴파일을 다시하는 방법도 있지만 메인 프로젝트가 .NET이라면 참조되는 모든 프로젝트를 다 바꿔야할 노릇.. 또 다른 방법은 COM+를 이용하여 독립적으로 만드는 것이다. 분리시키는 방법은 아래 주소해서 확인할 수 있다. http://support.microsoft.com/kb/281335 나의 경우는 Win32 COM DLL을 64비트 .NET 프로그램에서 참조하니 COM 객체를 제대로 호출하지 못하였습니다. 그래서 .NET 프로그램의 Target Machine을 x86으로 설정하니 제대로 COM 객체를 호출하였습니다.

[Pyinstaller] 실행 파일 관리자 권한 획득하기

고객사에서 일부 사용자에게서 프로그램 오류가 발생한다며 아래와 같이 에러 캡처를 보내왔습니다. 프로그램에서 로그를 남기기 위해 로그 파일을 생성하는데 권한의 문제로 로그 파일을 생성하지 못해 프로그램 오류가 발생한 것 같습니다. 처음에는 Python 코드에서 관리자 권한을 요청하는 코드를 넣으려고 했는데, 실제로 Stackoverflow를 찾아보면 이런 내용이 나옵니다. 프로그램이 관리자 권한으로 실행되지 않았다면 관리자 권한으로 다시 프로그램을 실행시키는 코드입니다. import os import sys import win32com.shell.shell as shell ASADMIN = 'asadmin' if sys.argv[-1] != ASADMIN: script = os.path.abspath(sys.argv[0]) params = ' '.join([script] + sys.argv[1:] + [ASADMIN]) shell.ShellExecuteEx(lpVerb='runas', lpFile=sys.executable, lpParameters=params) sys.exit(0) 하지만 개인적으로 이런 방식은 마음에 들지 않았고 조금 더 찾아보니 Pyinstaller로 exe 파일을 만들 때 옵션을 설정하여 관리자 권한을 요청하도록 할 수 있다고 합니다. --uac-admin을 옵션에 추가하면 프로그램 실행 시 관리자 권한을 요청할 수 있습니다. pyinstaller.exe --uac-admin sample.py 하지만 안타깝게도 이 방식은 원하는 대로 동작하지 않았습니다. 마지막으로 manifest 파일을 이용하여 시도해보았습니다. spec 파일을 이용하여 pyinstaller로 빌드하면 <실행 파일 이름>.manifest 라는 파일이 생성됩니다. 파일에서 아랫부분을 찾아볼 수 있습니다. <security> <re

초간단 프로그램 락 걸기

프로그램에 락을 걸 일이 생겨났다. 하드웨어 락을 걸면 쉬울텐데 그 정도는 아니고 프로그램의 실행 날짜를 제한 해 달라고 한다. 그래서 파일(license.lic)을 가지고 락을 걸리고 결정을 했다. 요구 사항은 아래와 같다. 1. license.lic 파일이 없으면 프로그램을 실행 할수 없게 한다. 2. 지정한 날짜를 넘어서는 프로그램을 실행 할수 없게 한다. 3. 사용자가 시스템 날짜를 되돌렸을때 인식하여 프로그램을 실행 할수 없게 한다. 음.... 1.번 문제는 사용자가 프로그램을 실행하기 위해서 license.lic 파일을 받아야만 한다. license.lic 파일에는 최근 실행 날짜/종료날짜 이런식으로 적도록 한다.(물론 내용은 암호화 한다.) 최근 실행날짜는 프로그램이 실행때마다 업데이트 하도록 하고 시스템 날짜와 비교하여 시스템 날짜가 최근 실행 날짜보다 이전의 날짜면 시스템 날짜를 되돌렸다고 인식하도록 한다.(3.번 문제 해결) 시스템 날짜와 종료 날짜를 비교하여 시스템 날짜가 종료 날짜를 넘으면 프로그램을 실행 할수 없도록 한다.(2.번 문제 해결)