본문 바로가기
C++ 공부

exception

by Dev_Hugh 2021. 8. 22.
728x90
반응형

Study about exception handling

  • try {} ~ catch() 문을 통한 구조화된 예외처리가 있음
  • try내 throw 가 나오면 객체로 떼어서 생각 구조화된 예외처리를 잘하자

함수속에서 throw 할 경우

void ExceptTest1()
{
    int a;

    if(!a)
    {
        throw a;
    }

}
void ExceptTest2()
{
    char ch;

    if(!ch)
    {
        throw ch;
    }
}
int main(void)
{
    try
    {
        ExceptTest1();
        ExceptTest2();
    }

    catch (int test1)
    {
        std::cout<<"ExceptTest1() Error" << std::endl;
    }
    catch (char test2)
    {
        std::cout<<"ExceptTest2() Error" << std::endl;
    }
}
  • try 내에선 정상적으로 돌아만가고 catch에선 함수속에서 실패했을때 throw를 던져도 알맞게 작동한다.
  • 즉 구조화를 잘해놓으면 개별화된게 눈에 잘 보여 코드 가독성이 좋아짐
  • 에러처리를 분류하는데 다중 if문이나 switch-case를 안써도 에러처리 가능해짐 (goto문으로 에러처리하던걸 찾아보면 이해쉬움)

예외 클래스

class MyException
{
public:
    MyException(int nCode, const char *psz)
    {
        m_nErrorCode = nCode;
        strcpy_s(m_sz, sizeof(m_sz), psz);
    }

    int GetErrorCode() const { return m_nErrorCode; }
    const char* GetMessager() const { return m_sz; }
private:
    int m_nErrorCode;
    char m_sz[128];
};
int main(void)
{
    try
    {
        int nInput(0);
        std::cout << "양의 정수 입력" << std::endl;
        std::cin >> nInput;

        if(nInput < 0)
        {
            MyException exp(10, "양의 정수를 입력해야합니다");
            throw exp
        }
    }

    catch(MyExceptoin &exp)
    {
        std::cout << "Error Code {" <<exp.GetErrorCode() << "}" << exp.GetMessager() << std::endl;
    }

    return 0;
}
  • thorw 던지는 구문을 main 바깥 함수로 작성하여 처리해도 가능하다.
  • 이과정에서 좋은점은 함수가 함수를 부르면 stack unwinding이 발생해 스택프레임이 여러개가 올라간다

stack unwinding 예제

void TestFunc1()
{
    // 가장 마지막에 호출된 함수가 예외를 던진다.
    std::cout << "***TestFunc1() - Begin***"<< std::endl;
    //throw 0;
    std::cout << "***TestFunc1() - End***"<< std::endl;
}
void TestFunc2()
{
    std::cout << "***TestFunc2() - Begin***"<< std::endl;
    TestFunc1();
    std::cout << "***TestFunc2() - End***"<< std::endl;
}
void TestFunc3()
{
    std::cout << "***TestFunc3() - Begin***"<< std::endl;
    TestFunc2();
    std::cout << "***TestFunc3() - End***"<< std::endl;
}

int main(void)
{
    try
    {
        TestFunc3();
    }
    catch(...)
    {
        // 함수들을 반환하지않고 스택이 즉시 풀려 흐림이 넘어온다.
        std::cout << "Exception handling"<< std::endl;
    }
    return 0;
}
  • main 위에 TestFunc3() -> TestFunc2() -> TestFunc1() 순으로 부르다면서 스택이 쌓인다 (부른순서대로 main위에 쌓임)
  • 함수를 반환하면서 맨 위부터 즉 TestFunc1() -> TestFunc2() -> TestFunc3() 순으로 스택을 풀어나간다
  • 그러나 TestFunc1()에서 예외를 던지면 함수반환없이 스택을 한번에 풀어준다. (매우 큰 장점)

stack unwinding 장점 (메모리 동적할당시)

  • C에서 malloc() 은 함수
  • C++에서 new 는 연산자다
  • 함수는 반환값을보고 메모리할당이 성공인지 실패인지 따질수있으나 연산자는 실패를 고려하겠다는 개념이아니다
  • 아래 예제를 통해 구조화된 예외처리방법과 장점 확인
class Test
{
public:
    Test(int nSize)
    {
        //메모리 할당 실패시 예외를 던진다
        m_pszData = new char[nSize];
    }
    ~Test()
    {
        delete[] m_pszData;
        std::cout << "정상적으로 객체 소멸" << std::endl;
    }
private:
    char *m_pszData;
};
int main(void)
{
    try
    {
        int nSize(0);
        std::cout << "Input size" << std::endl;
        std::cin >> nSize;
        Test test1(nSzie); // 여기서 객체 메모리 동적할당
    }

    catch(bad_alloc &exp)
    {
        //시스템이 밝힌 원인을 찾는다
        std::cout << exp.what() << std::endl;
        std::cout << "ErrorL: Test()" << std::endl;
    }
    return 0;
}
  • 만약 bad_alloc 클래스가 객체 메모리할당에서 실패시 예외를 던져주는 역할을할때 현재 main()함수의 try에서 객체 메모리 동적할당을 했는데 if문을 사용하지 않더라도 실패시 바로 catch() 문으로 간다.
  • 생성자의 경우 반환값이없어 객체생성이 실패인지 성공인지 모르고 생성자의 호출시점을 내가 정할수없기에 이를 확인할때 매우 유용하다.
728x90
반응형

'C++ 공부' 카테고리의 다른 글

댕글링 포인터 (Dangling Pointer)  (0) 2023.06.19
lambda  (0) 2021.08.22
template  (0) 2021.08.20
relational  (0) 2021.08.20
inheritance 04  (0) 2021.08.20