최신 C++ 예외 및 오류 처리 모범 사례

최신 C++의 경우 대부분의 시나리오에서 논리 오류와 런타임 오류를 모두 보고하고 처리하기 위해 기본적으로 예외를 사용합니다. 스택에 오류를 감지하는 함수와 오류를 처리할 컨텍스트가 있는 함수 간에 여러 함수 호출이 포함될 수 있는 경우 특히 그렇습니다. 예외는 호출 스택에 정보를 전달하는 오류를 감지하는 코드에 대해 잘 정의된 공식적인 방법을 제공합니다.

예외 코드에 예외 사용

프로그램 오류는 다음과 같은 두 가지 범주로 나뉘는 경우가 많습니다.

  • 첫 번째는 프로그래밍 실수로 인한 논리 오류입니다. 예를 들어 "범위를 벗어난 인덱스" 오류입니다.
  • 두 번째는 프로그래머가 제어할 수 없는 런타임 오류입니다. 예를 들어 "네트워크 서비스를 사용할 수 없음" 오류가 있습니다.

C 스타일 프로그래밍 및 COM에서 오류 보고는 특정 함수에 대한 오류 코드 또는 상태 코드를 나타내는 값을 반환하거나, 오류가 보고되었는지 여부를 확인하기 위해 모든 함수 호출 후에 호출자가 선택적으로 검색할 수 있는 전역 변수를 설정하여 관리됩니다. 예를 들어 COM 프로그래밍은 HRESULT 반환 값을 사용하여 호출자에게 오류를 전달합니다. 또한 Win32 API에는 호출 스택에서 보고한 마지막 오류를 검색하는 GetLastError 함수가 있습니다. 두 경우 모두 코드를 인식하고 적절하게 응답하는 일은 호출자가 담당합니다. 호출자가 오류 코드를 명시적으로 처리하지 않으면 프로그램이 경고 없이 충돌할 수 있습니다. 또는 잘못된 데이터를 사용하여 계속 실행하고 잘못된 결과를 생성할 수 있습니다.

최신 C++에서 예외 사용이 선호되는 이유는 다음과 같습니다.

  • 예외는 코드를 호출하여 오류 조건을 인식하고 처리하도록 강제합니다. 처리되지 않은 예외는 프로그램 실행을 중지합니다.
  • 예외는 호출 스택에서 오류를 처리할 수 있는 지점으로 이동합니다. 중간 함수는 예외가 전파되도록 할 수 있으며, 다른 레이어와 조정할 필요는 없습니다.
  • 예외 스택 해제 메커니즘은 잘 정의된 규칙에 따라 예외가 throw된 후 범위의 모든 개체를 삭제합니다.
  • 예외를 사용하면 오류를 감지하는 코드와 오류를 처리하는 코드 사이를 완전히 분리할 수 있습니다.

다음 간소화된 예제에서는 C++에서 예외를 throw하고 catch하는 데 필요한 구문을 보여 줍니다.

#include <stdexcept>
#include <limits>
#include <iostream>

using namespace std;

void MyFunc(int c)
{
    if (c > numeric_limits< char> ::max())
    {
        throw invalid_argument("MyFunc argument too large.");
    }
    //...
}

int main()
{
    try
    {
        MyFunc(256); //cause an exception to throw
    }

    catch (invalid_argument& e)
    {
        cerr << e.what() << endl;
        return -1;
    }
    //...
    return 0;
}

C++의 예외는 C# 및 Java와 같은 언어의 예외와 유사합니다. try 블록에서 예외가 throw되면 해당 형식이 예외의 형식과 일치하는 첫 번째 연결된 catch 블록에 의해 catch됩니다. 즉, 실행은 throw 문에서 catch 문으로 이동합니다. 사용할 수 있는 catch 블록이 없으면 std::terminate가 호출되고 프로그램이 종료됩니다. C++에서는 어떤 형식이든 throw될 수 있지만 std::exception에서 직접 또는 간접적으로 파생된 형식을 throw하는 것이 좋습니다. 이전 예제에서 예외 형식 invalid_argument<stdexcept> 헤더 파일의 표준 라이브러리에 정의되어 있습니다. C++는 예외가 throw될 경우 모든 리소스가 해제되도록 하기 위해 finally 블록을 제공하거나 요구하지 않습니다. 리소스 취득은 스마트 포인터를 사용하는 초기화(RAII) 관용구로 리소스 정리에 필요한 기능을 제공합니다. 자세한 내용은 방법: 예외 안전 디자인을 참조하세요. C++ 스택 해제 메커니즘에 대한 자세한 내용은 예외 및 스택 해제를 참조하세요.

기본 지침

어떤 프로그래밍 언어든 강력한 오류 처리는 까다로운 일입니다. 예외는 뛰어난 오류 처리를 지원하는 몇 가지 기능을 제공하지만 모든 작업을 대신할 수는 없습니다. 예외 메커니즘의 이점을 실현하려면 코드를 디자인할 때 예외를 염두에 두어야 합니다.

  • 어설션을 사용하여 항상 true이거나 항상 false여야 하는 조건을 확인합니다. 예외를 사용하여 발생할 수 있는 오류(예: 공용 함수의 매개 변수에 대한 입력 유효성 검사 오류)를 확인합니다. 자세한 내용은 예외와 어설션 비교 섹션을 참조하세요.
  • 오류를 처리하는 코드가 하나 이상의 중간 함수 호출로 오류를 감지하는 코드와 분리된 경우 예외를 사용합니다. 오류를 처리하는 코드가 오류를 감지하는 코드와 긴밀하게 결합된 경우 성능이 중요한 루프에서 오류 코드를 대신 사용할지 여부를 고려합니다.
  • 예외를 throw하거나 전파할 수 있는 모든 함수의 경우 강력한 보장, 기본 보증 또는 nothrow(noexcept) 보장의 세 가지 예외 보장 중 하나를 제공합니다. 자세한 내용은 방법: 예외 안전 디자인을 참조하세요.
  • 값으로 예외를 throw하고 참조로 catch합니다. 처리할 수 없는 항목은 catch하지 않습니다.
  • C++11에서 사용되지 않는 예외 사양은 사용하지 않습니다. 자세한 내용은 예외 사양 및 noexcept 섹션을 참조하세요.
  • 적용되는 경우 표준 라이브러리 예외 형식을 사용합니다. exception 클래스 계층 구조에서 사용자 지정 예외 형식을 파생합니다.
  • 예외가 소멸자 또는 메모리 할당 취소 함수에서 이스케이프되는 것을 허용하지 않습니다.

예외 및 성능

예외가 throw되지 않는 경우 예외 메커니즘의 성능 비용은 최소화됩니다. 예외가 throw되면 스택 통과 및 해제 비용은 함수 호출 비용과 거의 비슷합니다. try 블록을 입력한 후 호출 스택을 추적하려면 다른 데이터 구조가 필요하며, 예외가 throw될 경우 스택을 해제하려면 더 많은 지침이 필요합니다. 그러나 대부분의 시나리오에서는 성능 및 메모리 공간의 비용이 크지 않습니다. 예외가 성능에 미치는 악영향은 메모리가 제한된 시스템에서만 중요할 수 있습니다. 또는 성능이 중요한 루프에서는 오류가 정기적으로 발생할 가능성이 높고 이를 처리하는 코드와 보고하는 코드가 긴밀하게 결합되어 있습니다. 어떤 경우에도 프로파일링 및 측정 없이는 예외의 실제 비용을 알 수 없습니다. 드물게 비용이 많이 드는 경우라도 잘 설계된 예외 정책을 통해 얻을 수 있는 정확성 향상, 유지 관리 용이성 및 기타 이점과 비교하여 비용을 따져볼 수 있습니다.

예외와 어설션 비교

예외와 어설션은 프로그램에서 런타임 오류를 검색하기 위한 두 가지 고유한 메커니즘입니다. assert 문을 사용하여 개발 중에 항상 true이거나 모든 코드가 올바른 경우 항상 false여야 하는 조건을 테스트합니다. 오류는 코드의 항목을 수정해야 하므로 예외를 사용하여 이러한 오류를 처리할 필요가 없습니다. 프로그램이 런타임에 복구해야 하는 조건을 나타내지 않습니다. assert는 디버거에서 프로그램 상태를 검사할 수 있도록 문에서 실행을 중지합니다. 예외는 첫 번째 적절한 catch 처리기에서 실행을 계속합니다. 예외를 사용하여 코드가 올바른 경우에도 런타임에 발생할 수 있는 오류 조건(예: "파일을 찾을 수 없음" 또는 "메모리 부족")을 확인합니다. 복구에서 로그에 메시지를 출력하고 프로그램을 종료하더라도 예외는 이러한 조건을 처리할 수 있습니다. 항상 예외를 사용하여 공용 함수에 대한 인수를 확인합니다. 함수에 오류가 없는 경우에도 사용자가 전달할 수 있는 인수를 완전히 제어하지 못할 수 있습니다.

C++ 예외와 Windows SEH 예외 비교

C 및 C++ 프로그램 모두 Windows 운영 체제에서 SEH(구조적 예외 처리) 메커니즘을 사용할 수 있습니다. SEH의 개념은 C++ 예외의 개념과 유사합니다. 단, SEH는 trycatch 대신 __try , __except__finally 구문을 사용합니다. MSVC(Microsoft C++ 컴파일러)에서 C++ 예외는 SEH에 대해 구현됩니다. 그러나 C++ 코드를 작성할 때는 C++ 예외 구문을 사용합니다.

SEH에 대한 자세한 내용은 구조적 예외 처리(C/C++)를 참조하세요.

예외 사양 및 noexcept

예외 사양은 함수가 throw할 수 있는 예외를 지정하는 방법으로 C++에 처음 도입되었습니다. 그러나 예외 사양은 실제로 문제가 있는 것으로 판명되었으며 C++11 초안 표준에서는 더 이상 사용되지 않습니다. 함수에서 예외의 이스케이프를 허용하지 않음을 나타내는 throw()를 제외하고 throw 예외 사양은 사용하지 않는 것이 좋습니다. 사용되지 않는 양식의 예외 사양인 throw( type-name )를 사용해야 하는 경우 MSVC 지원이 제한됩니다. 자세한 내용은 예외 사양(throw)을 참조하세요. noexcept 지정자는 throw() 대신 선호되는 대안으로 C++11에 도입되었습니다.

참고 항목

방법: 예외 코드와 예외가 아닌 코드 간의 인터페이싱
C++ 언어 참조
C++ 표준 라이브러리