수색…
예외 잡기
try/catch
블록은 예외를 잡는 데 사용됩니다. try
섹션의 코드는 예외를 throw 할 수있는 코드이며 catch
절의 코드가 예외를 처리합니다.
#include <iostream>
#include <string>
#include <stdexcept>
int main() {
std::string str("foo");
try {
str.at(10); // access element, may throw std::out_of_range
} catch (const std::out_of_range& e) {
// what() is inherited from std::exception and contains an explanatory message
std::cout << e.what();
}
}
여러 catch
유형을 사용하여 여러 예외 유형을 처리 할 수 있습니다. 여러 catch
절이있는 경우 예외 처리 메커니즘은 코드에서 나타나는 순서대로 일치하려고 시도합니다.
std::string str("foo");
try {
str.reserve(2); // reserve extra capacity, may throw std::length_error
str.at(10); // access element, may throw std::out_of_range
} catch (const std::length_error& e) {
std::cout << e.what();
} catch (const std::out_of_range& e) {
std::cout << e.what();
}
공통 기본 클래스에서 파생 된 예외 클래스는 공통 기본 클래스에 대해 단일 catch
절을 사용하여 catch
할 수 있습니다. 위의 예는 두 개의 대체 할 수있는 catch
에 대한 조항 std::length_error
및 std::out_of_range
에 대해 하나의 절 std:exception
:
std::string str("foo");
try {
str.reserve(2); // reserve extra capacity, may throw std::length_error
str.at(10); // access element, may throw std::out_of_range
} catch (const std::exception& e) {
std::cout << e.what();
}
catch
절은 순서대로 시도되기 때문에보다 구체적인 catch 절을 먼저 작성해야합니다. 그렇지 않으면 예외 처리 코드가 호출되지 않을 수 있습니다.
try {
/* Code throwing exceptions omitted. */
} catch (const std::exception& e) {
/* Handle all exceptions of type std::exception. */
} catch (const std::runtime_error& e) {
/* This block of code will never execute, because std::runtime_error inherits
from std::exception, and all exceptions of type std::exception were already
caught by the previous catch clause. */
}
또 다른 가능성은 throw 된 객체를 잡는 catch-all 핸들러입니다.
try {
throw 10;
} catch (...) {
std::cout << "caught an exception";
}
예외 재발생 (전파)
때로는 catch (로그 작성 또는 경고 인쇄)와 같은 예외 상황을 처리하여 처리 할 상위 범위까지 버블 링하려고합니다. 이렇게하려면 catch 한 예외를 다시 throw 할 수 있습니다.
try {
... // some code here
} catch (const SomeException& e) {
std::cout << "caught an exception";
throw;
}
throw;
사용하기 throw;
인수없이 현재 catch 된 예외를 다시 throw합니다.
관리되는 std::exception_ptr
을 다시 throw하기 위해 C ++ 표준 라이브러리에는 프로그램에 <exception>
헤더를 포함하여 사용할 수있는 rethrow_exception
함수가 있습니다.
#include <iostream>
#include <string>
#include <exception>
#include <stdexcept>
void handle_eptr(std::exception_ptr eptr) // passing by value is ok
{
try {
if (eptr) {
std::rethrow_exception(eptr);
}
} catch(const std::exception& e) {
std::cout << "Caught exception \"" << e.what() << "\"\n";
}
}
int main()
{
std::exception_ptr eptr;
try {
std::string().at(1); // this generates an std::out_of_range
} catch(...) {
eptr = std::current_exception(); // capture
}
handle_eptr(eptr);
} // destructor for std::out_of_range called here, when the eptr is destructed
함수 블록에서 생성자를 시도하십시오.
이니셜 라이저 목록에서 예외를 잡는 유일한 방법은 다음과 같습니다.
struct A : public B
{
A() try : B(), foo(1), bar(2)
{
// constructor body
}
catch (...)
{
// exceptions from the initializer list and constructor are caught here
// if no exception is thrown here
// then the caught exception is re-thrown.
}
private:
Foo foo;
Bar bar;
};
기능 정상적인 기능을 위해 블록을 시도하십시오
void function_with_try_block()
try
{
// try block body
}
catch (...)
{
// catch block body
}
어느 것과 같은가?
void function_with_try_block()
{
try
{
// try block body
}
catch (...)
{
// catch block body
}
}
생성자와 소멸자의 경우, catch 블록이 어쨌든 예외를 다시 throw하는 것과는 동작이 다릅니다 (catch 블록 본문에 다른 throw가없는 경우 잡힌 블록).
main
함수는 다른 함수처럼 try 블록을 가질 수 있지만 main
의 함수 try 블록은 로컬이 아닌 정적 변수의 생성이나 정적 변수의 파기 중에 발생하는 예외를 catch하지 않습니다. 대신 std::terminate
가 호출됩니다.
함수 소멸자에서 블록 사용
struct A
{
~A() noexcept(false) try
{
// destructor body
}
catch (...)
{
// exceptions of destructor body are caught here
// if no exception is thrown here
// then the caught exception is re-thrown.
}
};
이것이 가능하더라도 스택 소멸 중에 호출 된 소멸자가 예외를 throw하는 것처럼 std::terminate
가 호출되는 것처럼 소멸자에서 throw하는 데 매우주의해야합니다.
모범 사례 : 가치에 의해 던지기, const 참조로 잡기
일반적으로 값으로 (포인터보다는) 던지지만 (const) 참조로 잡는 것이 우수 실행으로 간주됩니다.
try {
// throw new std::runtime_error("Error!"); // Don't do this!
// This creates an exception object
// on the heap and would require you to catch the
// pointer and manage the memory yourself. This can
// cause memory leaks!
throw std::runtime_error("Error!");
} catch (const std::runtime_error& e) {
std::cout << e.what() << std::endl;
}
참조로 catch하는 것이 좋은 방법 인 이유 중 하나는 catch 블록으로 전달 될 때 (또는 다른 catch 블록으로 전파 될 때) 오브젝트를 재구성 할 필요가 없기 때문입니다. 참조로 잡는 것은 또한 예외를 다형 적으로 처리 할 수있게하고 객체 조각을 피할 수있게합니다. 그러나 예외를 다시 throw e;
려면 (예 : throw e;
아래 예 참조) 객체를 조각 내기가 가능합니다 throw e;
문은 선언 된 형식으로 예외의 복사본을 만듭니다.
#include <iostream>
struct BaseException {
virtual const char* what() const { return "BaseException"; }
};
struct DerivedException : BaseException {
// "virtual" keyword is optional here
virtual const char* what() const { return "DerivedException"; }
};
int main(int argc, char** argv) {
try {
try {
throw DerivedException();
} catch (const BaseException& e) {
std::cout << "First catch block: " << e.what() << std::endl;
// Output ==> First catch block: DerivedException
throw e; // This changes the exception to BaseException
// instead of the original DerivedException!
}
} catch (const BaseException& e) {
std::cout << "Second catch block: " << e.what() << std::endl;
// Output ==> Second catch block: BaseException
}
return 0;
}
정보를 추가하거나 메시지를 수정하는 것과 같이 예외를 변경하지 않으려는 경우에는 const 참조를 사용하여 컴파일러에서 최적화를 수행하고 성능을 향상시킬 수 있습니다. 그러나 이것은 여전히 오브젝트 스플 라이스를 일으킬 수 있습니다 (위의 예에서 볼 수 있듯이).
경고 : catch
블록에 의도하지 않은 예외를 던지십시오. 특히 여분의 메모리 나 리소스를 할당하는 것과 관련이 있습니다. 예를 들어, 예외 문자열을 복사 할 때 메모리 부족으로 인해 logic_error
, runtime_error
또는 그 하위 클래스를 생성 logic_error
bad_alloc
발생하고 예외 마스크가 설정된 로깅 중에는 I / O 스트림이 throw 될 수 있습니다.
중첩 예외
예외 처리 중에 저수준 함수 (예 : 파일 시스템 오류 또는 데이터 전송 오류)에서 일반 예외를 catch하고 일부 상위 수준 연산을 수행 할 수있는보다 구체적인 상위 예외를 throw하는 일반적인 사용 사례가 있습니다 수행 할 수 없음 (예 : 웹에 사진을 게시 할 수 없음). 이렇게하면 예외 처리가 상위 수준 작업의 특정 문제에 대응할 수 있고 오류 만있는 메시지 만 있으면 프로그래머는 응용 프로그램에서 예외가 발생한 위치를 찾을 수 있습니다. 이 솔루션의 단점은 예외 콜 스택이 잘리고 원래 예외가 손실된다는 것입니다. 이로 인해 개발자는 원래 예외 텍스트를 수동으로 새로 작성된 텍스트에 포함시킵니다.
중첩 예외는 원인을 설명하는 하위 예외를이 특별한 경우에서의 의미를 설명하는 상위 예외에 첨부하여 문제를 해결하는 것을 목표로합니다.
std::nested_exception
, 감사 둥지 예외로 할 수 std::throw_with_nested
:
#include <stdexcept>
#include <exception>
#include <string>
#include <fstream>
#include <iostream>
struct MyException
{
MyException(const std::string& message) : message(message) {}
std::string message;
};
void print_current_exception(int level)
{
try {
throw;
} catch (const std::exception& e) {
std::cerr << std::string(level, ' ') << "exception: " << e.what() << '\n';
} catch (const MyException& e) {
std::cerr << std::string(level, ' ') << "MyException: " << e.message << '\n';
} catch (...) {
std::cerr << "Unkown exception\n";
}
}
void print_current_exception_with_nested(int level = 0)
{
try {
throw;
} catch (...) {
print_current_exception(level);
}
try {
throw;
} catch (const std::nested_exception& nested) {
try {
nested.rethrow_nested();
} catch (...) {
print_current_exception_with_nested(level + 1); // recursion
}
} catch (...) {
//Empty // End recursion
}
}
// sample function that catches an exception and wraps it in a nested exception
void open_file(const std::string& s)
{
try {
std::ifstream file(s);
file.exceptions(std::ios_base::failbit);
} catch(...) {
std::throw_with_nested(MyException{"Couldn't open " + s});
}
}
// sample function that catches an exception and wraps it in a nested exception
void run()
{
try {
open_file("nonexistent.file");
} catch(...) {
std::throw_with_nested( std::runtime_error("run() failed") );
}
}
// runs the sample function above and prints the caught exception
int main()
{
try {
run();
} catch(...) {
print_current_exception_with_nested();
}
}
가능한 출력 :
exception: run() failed
MyException: Couldn't open nonexistent.file
exception: basic_ios::clear
std::exception
에서 상속받은 예외 만 사용하면 코드를 단순화 할 수도 있습니다.
std :: uncaught_exceptions
C ++ 17은 int std::uncaught_exceptions()
(제한된 bool std::uncaught_exception()
대체)를 사용하여 현재 얼마나 많은 예외가 catch되지 않는지를 알 수 있습니다. 이를 통해 클래스는 스택 되감기 중에 파괴되는지 여부를 판별 할 수 있습니다.
#include <exception>
#include <string>
#include <iostream>
// Apply change on destruction:
// Rollback in case of exception (failure)
// Else Commit (success)
class Transaction
{
public:
Transaction(const std::string& s) : message(s) {}
Transaction(const Transaction&) = delete;
Transaction& operator =(const Transaction&) = delete;
void Commit() { std::cout << message << ": Commit\n"; }
void RollBack() noexcept(true) { std::cout << message << ": Rollback\n"; }
// ...
~Transaction() {
if (uncaughtExceptionCount == std::uncaught_exceptions()) {
Commit(); // May throw.
} else { // current stack unwinding
RollBack();
}
}
private:
std::string message;
int uncaughtExceptionCount = std::uncaught_exceptions();
};
class Foo
{
public:
~Foo() {
try {
Transaction transaction("In ~Foo"); // Commit,
// even if there is an uncaught exception
//...
} catch (const std::exception& e) {
std::cerr << "exception/~Foo:" << e.what() << std::endl;
}
}
};
int main()
{
try {
Transaction transaction("In main"); // RollBack
Foo foo; // ~Foo commit its transaction.
//...
throw std::runtime_error("Error");
} catch (const std::exception& e) {
std::cerr << "exception/main:" << e.what() << std::endl;
}
}
산출:
In ~Foo: Commit
In main: Rollback
exception/main:Error
맞춤 예외
원시 값을 예외로 던져서는 안되며 대신 표준 예외 클래스 중 하나를 사용하거나 직접 만듭니다.
자신의 예외 클래스를 std::exception
에서 상속받는 것은 좋은 방법입니다. 다음은 std::exception
에서 직접 상속받은 사용자 정의 예외 클래스입니다.
#include <exception>
class Except: virtual public std::exception {
protected:
int error_number; ///< Error number
int error_offset; ///< Error offset
std::string error_message; ///< Error message
public:
/** Constructor (C++ STL string, int, int).
* @param msg The error message
* @param err_num Error number
* @param err_off Error offset
*/
explicit
Except(const std::string& msg, int err_num, int err_off):
error_number(err_num),
error_offset(err_off),
error_message(msg)
{}
/** Destructor.
* Virtual to allow for subclassing.
*/
virtual ~Except() throw () {}
/** Returns a pointer to the (constant) error description.
* @return A pointer to a const char*. The underlying memory
* is in possession of the Except object. Callers must
* not attempt to free the memory.
*/
virtual const char* what() const throw () {
return error_message.c_str();
}
/** Returns error number.
* @return #error_number
*/
virtual int getErrorNumber() const throw() {
return error_number;
}
/**Returns error offset.
* @return #error_offset
*/
virtual int getErrorOffset() const throw() {
return error_offset;
}
};
던지기 예 :
try {
throw(Except("Couldn't do what you were expecting", -12, -34));
} catch (const Except& e) {
std::cout<<e.what()
<<"\nError number: "<<e.getErrorNumber()
<<"\nError offset: "<<e.getErrorOffset();
}
멍청한 오류 메시지를 던지거나 오류가 무엇인지 정확하게 나타내는 다른 값을 던지기 만하면 오류 처리가 훨씬 효율적이고 의미있게됩니다.
오류 메시지를 멋지게 처리 할 수있는 예외 클래스가 있습니다. std::runtime_error
이 클래스에서 상속받을 수도 있습니다.
#include <stdexcept>
class Except: virtual public std::runtime_error {
protected:
int error_number; ///< Error number
int error_offset; ///< Error offset
public:
/** Constructor (C++ STL string, int, int).
* @param msg The error message
* @param err_num Error number
* @param err_off Error offset
*/
explicit
Except(const std::string& msg, int err_num, int err_off):
std::runtime_error(msg)
{
error_number = err_num;
error_offset = err_off;
}
/** Destructor.
* Virtual to allow for subclassing.
*/
virtual ~Except() throw () {}
/** Returns error number.
* @return #error_number
*/
virtual int getErrorNumber() const throw() {
return error_number;
}
/**Returns error offset.
* @return #error_offset
*/
virtual int getErrorOffset() const throw() {
return error_offset;
}
};
참고로 what()
함수를 기본 클래스 ( std::runtime_error
)에서 재정의하지 않았습니다. 즉 기본 클래스의 what()
합니다. 추가 의제가있는 경우 재정의 할 수 있습니다.