변수 메시지로 std :: exceptions를 던지는 방법은 무엇입니까?


121

다음은 예외에 몇 가지 정보를 추가하고 싶을 때 자주하는 작업의 예입니다.

std::stringstream errMsg;
errMsg << "Could not load config file '" << configfile << "'";
throw std::exception(errMsg.str().c_str());

더 좋은 방법이 있습니까?


10
어떻게 이런 식으로 작업 할 수 있었는지 궁금합니다 . arg std∷exception가있는 생성자가 없습니다 char*.
Hi-Angel

2
나는 같은 것을 궁금합니다. C ++에 대한 비표준 MS 확장일까요? 아니면 C ++ 14에서 새로운 것이 있을까요? 현재 문서에 따르면 std :: exception 생성자는 인수를 사용하지 않습니다.
Chris Warth

1
네,하지만 std::string소요 암시 적 생성자가 const char*...
브라이스 M. 뎀시

6
@Chris Warth MS std::exception의 자식 클래스의 비하인드 스토리 구현의 일부인 것으로 보이며 std::runtime_errorstd::logic_error. 그렇다 표준에 의해 정의 된 것과의 MSVS '버전은 <exception>또한 두 개 더 생성자, 하나 개의 복용 포함 (const char * const &)하고 다른 복용 (const char * const &, int). 개인 변수를 설정하는 데 사용됩니다 const char * _Mywhat. 만약 _Mywhat != nullptr다음, what()그것을 반환 기본값. 그것에 의존하는 코드는 아마도 이식성이 없을 것입니다.
저스틴 시간 - 분석 재개 모니카

답변:


49

내 해결책은 다음과 같습니다.

#include <stdexcept>
#include <sstream>

class Formatter
{
public:
    Formatter() {}
    ~Formatter() {}

    template <typename Type>
    Formatter & operator << (const Type & value)
    {
        stream_ << value;
        return *this;
    }

    std::string str() const         { return stream_.str(); }
    operator std::string () const   { return stream_.str(); }

    enum ConvertToString 
    {
        to_str
    };
    std::string operator >> (ConvertToString) { return stream_.str(); }

private:
    std::stringstream stream_;

    Formatter(const Formatter &);
    Formatter & operator = (Formatter &);
};

예:

throw std::runtime_error(Formatter() << foo << 13 << ", bar" << myData);   // implicitly cast to std::string
throw std::runtime_error(Formatter() << foo << 13 << ", bar" << myData >> Formatter::to_str);    // explicitly cast to std::string

1
이런 식으로하는 방법을 찾고있었습니다. 그러나 아마 (연산자 오버로딩) 오버 방지하기 위해 명시 적 기능에 연산자를 >> 변경됩니다
로마 플라실

3
이것과 std :: stringstream의 차이점은 무엇입니까? 그것은 stringstream을 포함하는 것처럼 보이지만 (내가 말할 수있는 한) 추가 기능이 없습니다.
matts1 2016 년

2
일반적으로 100 % 안전한 방법은 아닙니다. : 표준 : : 이제 stringstream 방법은 꽤 좋은 여기에 설명 인 제외하고 ... 문제 던질 수 boost.org/community/error_handling.html
아서 P. 골루 뵤프

1
@ ArthurP.Golubev 그러나이 경우 Formatter () 인스턴스는 또한 장면 뒤에서 stringstream을 인스턴스화하므로 예외가 발생할 수 있습니다. 그렇다면 차이점은 무엇입니까?
Zuzu Corneliu

추가 된 유일한 기능은 ConvertToString 트릭과 문자열로의 명시 적 캐스트입니다. 어쨌든 좋습니다. ;)
Zuzu Corneliu

178

표준 예외는 다음에서 구성 할 수 있습니다 std::string.

#include <stdexcept>

char const * configfile = "hardcode.cfg";
std::string const anotherfile = get_file();

throw std::runtime_error(std::string("Failed: ") + configfile);
throw std::runtime_error("Error: " + anotherfile);

기본 클래스 std::exception는 이렇게 구성 할 수 없습니다 . 구체적인 파생 클래스 중 하나를 사용해야합니다.


27

같은 다른 예외가있다 runtime_error, range_error, overflow_error, logic_error, 등 당신은 생성자에 문자열을 전달해야하고, 당신은 당신이 당신의 메시지에 원하는대로 연결할 수 있습니다. 그것은 단지 문자열 연산입니다.

std::string errorMessage = std::string("Error: on file ")+fileName;
throw std::runtime_error(errorMessage);

다음 boost::format과 같이 사용할 수도 있습니다 .

throw std::runtime_error(boost::format("Error processing file %1") % fileName);

위의 boost :: format 버전은 명시 적 변환없이 컴파일되지 않습니다. 예 : runtime_error ((boost :: format ( "Text % 1"% 2) .str ())). C ++ 20에는 유사한 기능을 제공하는 std :: format이 도입되었습니다.
Digicrat

17

다음 클래스는 매우 유용 할 수 있습니다.

struct Error : std::exception
{
    char text[1000];

    Error(char const* fmt, ...) __attribute__((format(printf,2,3))) {
        va_list ap;
        va_start(ap, fmt);
        vsnprintf(text, sizeof text, fmt, ap);
        va_end(ap);
    }

    char const* what() const throw() { return text; }
};

사용 예 :

throw Error("Could not load config file '%s'", configfile.c_str());

4
나쁜 관행 IMO, 최적화를 위해 구축 된 표준 라이브러리가 이미 있는데 왜 이와 같은 것을 사용합니까?
Jean-Marie Comets 2012 년

3
throw std::runtime_error(sprintf("Could not load config file '%s'", configfile.c_str()))
Jean-Marie Comets 2012 년

4
throw std::runtime_error("Could not load config file " + configfile);( std::string필요한 경우 하나 또는 다른 인수를로 변환 ).
Mike Seymour 2012 년

9
@MikeSeymour 예,하지만 중간에 문자열을 넣고 특정 정밀도로 숫자를 형식화해야하는 경우 더 추악 해집니다. 명확성 측면에서 좋은 오래된 형식 문자열을이기는 것은 어렵습니다.
Maxim Egorushkin 2012 년

2
@MikeSeymour 내가 게시 한 코드가 시간보다 빠를 수 있다는 데 동의 할 수 있습니다. 이식 가능하게 형식이 안전 printf하고 친구가 C ++ 11에 임박했습니다. 고정 크기 버퍼는 축복이자 저주입니다. 리소스가 부족한 상황에서 실패하지는 않지만 메시지가 잘릴 수 있습니다. 오류 메시지를 자르는 것이 더 나은 옵션이라고 생각합니다. 또한 형식 문자열의 편리함은 여러 언어에서 입증되었습니다. 그러나 당신이 옳습니다. 그것은 주로 취향의 문제입니다.
Maxim Egorushkin 2012 년

11

C ++ 14 ( operator ""s) 인 경우 문자열 리터럴 연산자 사용

using namespace std::string_literals;

throw std::exception("Could not load config file '"s + configfile + "'"s);

또는 C ++ 11에서 직접 정의하십시오. 예를 들어

std::string operator ""_s(const char * str, std::size_t len) {
    return std::string(str, str + len);
}

그러면 throw 문은 다음과 같습니다.

throw std::exception("Could not load config file '"_s + configfile + "'"_s);

멋지고 깨끗해 보입니다.


2
이 오류가 발생했습니다. c ++ \ 7.3.0 \ bits \ exception.h | 63 | 참고 : 'std :: exception :: exception (std :: __ cxx11 :: basic_string <char>) 호출에 대해 일치하는 함수가 없습니다.
HaseeB Mir

@Shreevardhan이 설명하는 동작은 MSVC ++에서 컴파일하지만 std 라이브러리에 정의되어 있지 않습니다.
요헨

0

정말 좋은 방법은 예외에 대한 클래스 (또는 클래스)를 만드는 것입니다.

다음과 같은 것 :

class ConfigurationError : public std::exception {
public:
    ConfigurationError();
};

class ConfigurationLoadError : public ConfigurationError {
public:
    ConfigurationLoadError(std::string & filename);
};

그 이유는 예외가 문자열을 전송하는 것보다 훨씬 더 바람직하기 때문입니다. 오류에 대해 서로 다른 클래스를 제공하면 개발자가 특정 오류를 해당 방식으로 처리 할 수있는 기회를 제공합니다 (단지 오류 메시지를 표시하는 것이 아님). 예외를 포착하는 사람들은 계층 구조를 사용하는 경우 필요한만큼 구체적 일 수 있습니다.

a) 구체적인 이유를 알아야 할 수도 있습니다.

} catch (const ConfigurationLoadError & ex) {
// ...
} catch (const ConfigurationError & ex) {

a) 다른 사람은 세부 사항을 알고 싶어하지 않습니다

} catch (const std::exception & ex) {

이 주제에 대한 영감은 https://books.google.ru/books?id=6tjfmnKhT24C 9 장 에서 찾을 수 있습니다.

또한 사용자 지정 메시지를 제공 할 수도 있지만주의 해야합니다std::stringstd::stringstream . 예외가 발생할 수있는 방법 중 하나 또는 다른 방법으로 메시지를 작성하는 것은 안전하지 않습니다 .

일반적으로 예외 생성자에서 메모리를 할당하는지 (C ++ 방식으로 문자열 작업) 또는 throw 직전에 차이가 없습니다. std::bad_alloc 예외가 발생하기 직전에 . 정말 원하는 것보다 먼저 예외가 발생 될 수 있습니다.

따라서 스택에 할당 된 버퍼 (Maxim의 답변과 같이)가 더 안전한 방법입니다.

http://www.boost.org/community/error_handling.html 에서 매우 잘 설명됩니다 .

따라서 더 좋은 방법은 특정 유형의 예외이며 형식화 된 문자열 작성을 피하는 것입니다 (적어도 던질 때).


0

내 사용자 지정 예외에 대한 사용자 지정 오류 메시지를 만들면 코드가 추악하다는 점에서 유사한 문제가 발생했습니다. 이것이 내 해결책이었습니다.

class MyRunTimeException: public std::runtime_error
{
public:
      MyRunTimeException(const std::string &filename):std::runtime_error(GetMessage(filename)) {}
private:
      static std::string GetMessage(const std::string &filename)
     {
           // Do your message formatting here. 
           // The benefit of returning std::string, is that the compiler will make sure the buffer is good for the length of the constructor call
           // You can use a local std::ostringstream here, and return os.str()
           // Without worrying that the memory is out of scope. It'll get copied
           // You also can create multiple GetMessage functions that take all sorts of objects and add multiple constructors for your exception
     }
}

이것은 메시지를 만드는 논리를 분리합니다. 나는 원래 what ()을 재정의하는 것에 대해 생각했지만, 당신은 당신의 메시지를 어딘가에 포착해야한다. std :: runtime_error에 이미 내부 버퍼가 있습니다.


0

어쩌면 이거?

throw std::runtime_error(
    (std::ostringstream()
        << "Could not load config file '"
        << configfile
        << "'"
    ).str()
);

임시 ostringstream을 만들고 필요에 따라 << 연산자를 호출 한 다음이를 둥근 대괄호로 감싸고 평가 된 결과 (ostringstream)에 대해 .str () 함수를 호출하여 임시 std :: string을 생성자에 전달합니다. runtime_error의.

참고 : ostringstream과 문자열은 r- 값 임시이므로이 줄이 끝나면 범위를 벗어납니다. 예외 객체의 생성자는 복사 또는 (더 나은) 이동 의미 체계를 사용하여 입력 문자열을 가져와야합니다.

추가 : 필자는이 접근 방식을 "모범 사례"로 간주 할 필요는 없지만 작동하며 신속하게 사용할 수 있습니다. 가장 큰 문제 중 하나는이 방법에 힙 할당이 필요하므로 연산자 <<가 발생할 수 있다는 것입니다. 당신은 아마 그런 일이 일어나기를 원하지 않을 것입니다. 그러나 그 상태에 들어가면 걱정할 문제가 더 많을 것입니다!

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.