Post List

레이블이 Cpp_Basic인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Cpp_Basic인 게시물을 표시합니다. 모든 게시물 표시

2015년 3월 25일 수요일

item 04: 타입 추론 결과를 확인 할 수 있는 방법

작가
Meyers, Scott
출판
O'ReillyMedia
발매
2014.12.12
평점








Effective Modern C++ - Scott Meyers
Item 4 : Know how to view deduced types.

item 4. 타입 추론 결과를 확인 할 수 있는 방법

타입 추론 (Type decucing) 된 결과를 확인 할 수 있는 방법은 크게 3가지가 있다.
방법으로도 3가지이지만, 시기적으로도 각각 다르다.

1. IDE Editor를 사용 (Coding 시 확인 가능)

 요즘 IDE들이 워낙 잘 나와서 마우스만 위에 올려도 추론된 결과를 다 알려준다.


2. Compiler Diagnostics (Compile 할때 확인 가능)

 컴파일 단계에서 에러로 추론 결과를 알려준다.



3. Runtime Output (실행 중에 확인 가능)

 실행중에 printf 나 std::cout 등을 이용하여 확인이 가능하다.
 가장 쉬운 방법은 typeid 와 std::type_info::name 을 이용하는 방법이다.

const int CI = 42;
auto x = CI;
auto y = &CI;

std::cout << typeid(x).name() << std::endl; // int
std::cout << typeid(y).name() << std::endl; // int const *

결과는 Compiler마다 조금 다르게 출력해 줄수도 있다.
MS사의 Visual Studio는 친절하게 타입을 알려주지만, GCC는 좀 다르게 표시되기도 한다.
예를 들어서 y에 대한 결과를 PKi로 표시한다. (pointer to const int)

조금 더 복잡한 예제를 한번 보자.

class Widget {
public:
Widget(int p) : i(p) {}
int i;
};

template<typename T>
void f(const T& param)
{
cout << "T =     " << typeid(T).name() << endl;
cout << "param = " << typeid(param).name() << endl;
}

std::vector<Widget> createVec()
{
return std::vector<Widget> { 1, 2, 3, 4 };
}

const auto vw = createVec();

if (!vw.empty())
{
f(&vw[0]);
}

T     = class Widget const *
param = class Widget const *

결과는 이와 같이 나왔다.
param의 타입은 const T& 인데, T와 타입이 같다는 것이 이상하다.
item 1에서 타입추론에 대해서 설명한 방법과 똑같은 방법으로 추론을 했기 때문이다.
이 결과는 옳다고 해야 할지 아니라고 해야 할지...

더 확실한 결과가 필요하다면 Boost.TypeIndex 라이브러리를 사용하면 된다.

#include <boost/type_index.hpp>


template<typename T>
void f(const T& param)
{
    using namespace std;
    using boost::typeindex::type_id_with_cvr;

    cout << "T = "     << type_id_with_cvr<T>().pretty_name() << endl;
    cout << "param = " << type_id_with_cvr<decltype(param)>().pretty_name() << endl;
}

T = class Widget const *
param = class Widget const * const &


Things to Remember

* 타입추론 결과를 알 수 있는 방법으로는 IDE, Compiler의 Error Message, Run-time에서의 Boost.TypeIndex 등의 라이브러리를 이용하는 방법이 있다.

* 위 도구들을 이용하여 확인한 결과가 100% 정확한 것은 아니다.
  타입 추론 규칙에 대해서 잘 알고 있는 것이 중요하다.

2015년 3월 24일 화요일

[C/C++] time_t, tm 사용하기

C / C++ 에서 시간, 날짜 관련된 class 및 struct 들은 많다.
Modern C++에서는 std::chrono를 사용하면 직관적으로 편리하게 사용이 가능하지만,
최신 컴파일러가 아닌 환경에서 Windows , Linux에서 같이 사용되어야 할 코드에서는
time_ttm을 이용하는게 최선이 아닐까 한다.

1. time_t의 용도

 * 1970년 1월 1일 자정 이후 경과된 시간(초 단위)을 가지고 있다.
 * 현재 시간을 가져오기에 편리하다. (관련 함수 제공)
 * 시간간의 간격 계산이 편리하다.

#include <time.h>

time_t tNow = time(NULL);        // 현재 시간 가져오기

time_t mktime(&sTm_UD);

int nDiff = difftime(tUD, tNow); // 두 시간 간의 차이를 초(sec) 단위로 계산한다.

tNow += 150 * 60 * 60 * 24; // 150  날짜 : 150일 x 24시간 x 60분 x 60초

2. tm의 용도

 * 1900년 이후 시간의 표현이 가능하다.
 * 년, 월, 일, 시, 분, 초 의 사용자가 직접 입력이 가능하다.

// tm 특정 날짜로 만들기
tm sTm_UD;
memset(&sTm_UD, 0, sizeof(tm));
sTm_UD.tm_year = 200;
sTm_UD.tm_mon = 2;
sTm_UD.tm_mday = 5;

printf(asctime(&sTm_UD));
cout << 1900 + sTm_UD.tm_year << " - " << 1 + sTm_UD.tm_mon << " - " << sTm_UD.tm_mday << endl;


이렇게 두 타입간의 장점이 다르다.
이제 두 타입간의 변환만 할 수 있으면 충분히 어떤 용도로도 활용이 가능 할 것이다.

3. time_t <-> tm

time_t tNow = time(NULL);     // 현재 시간 가져오기

// time_t to tm
tm sTm_Now;
localtime64_s(&sTm_Now, &tNow); // time_t -> tm

time_t tUD = mktime(&sTm_Now);  // tm -> time_t

localtime64_s( ) 와  mktime( )를 이용하면 서로 형변환이 가능하다.


2015년 2월 9일 월요일

C++ Exception Handling (예외 처리) 와 Stack Unwinding (스택 풀기)

* Exception Handling (예외 처리)

프로그램 실행 도중에 비정상적인 상황이 발생하는 것을 예외(Exception)이라고 하고,
그 상황을 처리하는 과정을 예외 처리(Exception Handling)이라고 한다.
C++에서 예외를 처리하는 구문은 아래와 같다.

try
{
  if (/* Exception Condition */)
    throw new std::exception("Error Description");
}
catch (std::exception e)
{
std::cout << "Exception : " << e.what() << std::endl;
}

예외처리를 하지 않을 경우 실행할 일반적인 code 들은 try {...절 안에 둔다.
예외가 발생하는 상황에서는 throw를 이용하여 예외를 발생시키면 catch (...) {...}절 에서 해당 예외를 처리해주는 code를 넣으면 된다.
catch (...) {...}절은 예외 종류에 따라 여러개를 둘 수 있다.
다른 예외들을 하나의 catch (...) {...}절에서 처리 할 필요없이 다른 절에서 받아서 각각 처리가 가능하다.

예외가 발생하는 대표적인 예는 특정 수를 0 으로 나눌 경우이다.

#include <iostream>

void main()
{
    int a = 9;
    int b = 0;
    int c = a / b;
    std::cout << c << std::endl;
}

위의 짧은 Code 실행시 Debug 모드에서는 아래와 같은 오류를 발생시키며, Release 모드로 실행시킬땐 비정상 종료시키는 창까지 뜬다.



굳이 예외를 쓰지않고 프로그램 로직으로도 처리하는 방법도 있다.

void main()
{
    int a = 9;
    int b = 0;
    int c = 0;

    if (b != 0)
    {
       c = a / b;
       std::cout << c << std::endl;
    }
    else
       std::cout << "Exception : Divide by zero" << std::endl;
}

하지만 이렇게 하면 예외가 발생할때마다 처리해 주어야 할 code는 늘어난다.
어차피 예외로 처리해도 늘어나긴 하지만, code 상에서 예외 처리 구문과 원래 프로그램 code의 구분이 명확하지가 않아서 나중에 보고 파악하는게 어려워진다.

그럼 try {...catch (...) {...}를 이용하여 예외 처리하는 형식으로 고쳐보자.

#include <iostream>

void main()
{
    int a = 9;
    int b = 0;
    int c = 0;

    try
    {
       if (b == 0) throw b;
       c = a / b;
       std::cout << c << std::endl;
    }
    catch (int divided)
    {
       std::cout << "Exception : Divide by " << divided << std::endl;
    }
}

원래 code보다는 좀 더 분명하게 예외 처리하는 부분을 명확히 구분 할 수 있게 되었다.
되도록이면 try {...} 절에 들어가는 code를 최소화로 하는게 나중에 code를 다시 봤을 때 이해하기가 편하다.

하지만 대부분의 경우 저렇게 try {...} 안에서 바로 throw로 예외를 전달하는 경우는 잘 없다.
보통 함수에서 예외를 발생시키고 해당 함수를 호출하는 측에서 try {...catch (...) {...}절을 이용하여 예외를 처리한다.

통상적으로 많이 사용하는 형태는 아래와 같다.

#include <iostream>

template <typename T>
T Divide(T a, T b)
{
    if (b == 0) throw std::exception("Divide by zero");
    return a / b;
}

void main()
{
    int a = 9;
    int b = 0;
    int c = 0;

    try
    {
       c = Divide<int>(a, b);
       std::cout << c << std::endl;
    }
    catch (std::exception e)
    {
       std::cout << e.what() << std::endl;
    }
}

* 함수 선언에 예외 정보 포함시키기

함수를 선언할 때 함수 원형 뒤쪽에 이 함수에서 발생 할 수 있는 예외의 종류를 지정할 수 있다.

throw( ... ) 에서 괄호 안에 예외의 타입을 넣어주면 된다.
이걸 안적어준 경우는 임의외 예외를 던질 수 있다는 의미이다.

void func(int a) throw(int);

예외가 2개 이상일 경우 콤마(,)로 구분하여 나열한다.

void func(int a) throw(char *, int);

괄호안에 아무것도 안적어주면 예외를 던지지 않는 함수라는 의미이다.

void func(int a) throw();

* 표준 예외 클래스

자주 발생하는 예외에 대해서 미리 정의한 예외 클래스 들이 있다.
각각의 예외별로 다른 header에 정의되어 있지만,
#include <exception>만 해주더라도 대부분의 경우 사용이 가능하다.

#include <iostream>
#include <new>

void main()
{
    char* ptr;
    try
    {
       ptr = new char[(~unsigned int((int)0) / 2) - 1];
       delete[] ptr;
    }
    catch (std::bad_alloc &ba)
    {
       std::cout << ba.what() << std::endl;
    }
}

표준 예외 클래스 몇개만 나열해 보겠다.

bad_alloc: 메모리 할당 오류로서 new 연산에서 발생 <new>
bad_cast : 형변환 오류로서 dynamic_cast 에서 발생  <typeinfo.h>
bad_type_id : typeid에 대한 피 연산자가 널 포인터인 경우 발생
bad_exception : 예기치 못한 예외로서 함수 발생 목록에 있지 않는 예외
bad_logic_error : 클래스 논리 오류로 invalid_argumentlength_errorout_of_range 의 기본 <stdexcept>
runtime_error : 실행 오류로 overflow_errorunderflow_error의 기본 <stdexcept>

* Stack Unwinding (스택 풀기)

위에서 본 예제들 같이 예외를 catch해줘서 처리를 해 줘야하는데, 만약 catch절이 없는 상태에서 예외가 발생하면 어떻게 될까 ?
그러면 해당 함수를 호출한 곳으로 그 예외를 넘기게 된다.
만약 그 함수에서도 예외를 catch하여 처리하지 못하면 또 그 위에 함수로 계속해서 예외는 전달된다.
이렇게 예외를 처리하지 못하고 상위 함수로 계속 전달하는 과정을 스택 풀기(Stack Unwinding) 이라고 한다.
왜 이런 이름이 붙여 졌는지를 간단히 설명하자면...
함수를 호출하게 되면 이전 함수의 정보를 Stack에 넣어둔다. 그래서 함수를 계속해서 호출하게되면 그 정보들이 Stack에 계속 누적되고, 함수의 처리가 끝나면 Stack에서 정보를 찾아서 자신을 호출한 곳으로 돌아간다.
예외 처리를 위해서 계속해서 Stack 을 올라가면서 호출한 함수들의 실행을 풀어버린다는 뜻이다.

#include <iostream>

void f1() { throw 0; }
void f2() { f1(); }
void f3() { f2(); }
void f4() { f3(); }

void main()
{
    try
    {
       f4();
    }
    catch (int e)
    {
       std::cout << e << std::endl;
    }

}

위 code 실행시 호출된 순서대로 Stack에 쌓이게 된다.



main() -> f4() -> f3() -> f2() -> f1() 의 순서대로 호출된다.
그런데 f4()에서 오류가 발생하면 해당 오류를 처리해 줄수 있는 함수까지 역순으로 찾아간다.
먼저 f1()를 Stack에서 꺼내서 해제한 후
f2()를 꺼낸 뒤 오류 처리를 해 줄수 있는지 보고 없으면 f2()도 해제 한 후
f3() -> f4() -> main() 순으로 Stack에서 해제하게 된다.

위 code를 실행하면 f1에서 발생한 예외를 처리하기 위하여 f2 -> f3 -> f4 를 거쳐서 main() 에서 처리를 하게 된다.

* 참조한 곳

MSDN의 여러 곳 ( 다 나열하기엔 너무 많아서... )
EXYNOA NETWORK : http://blog.eairship.kr/179
알고보면 재미있는 IT&RIVEW BLOG : http://algobomyun.tistory.com/265