可変メッセージで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コンストラクターは引数を取りません。
クリスウォース

1
はい、ただし... std::stringをとる暗黙のコンストラクタがありconst char*ます
Brice M. Dempsey

6
@クリスヴァースMS「の舞台裏実装の一部のように見えるstd::exceptionの子クラス」とのそれぞれのバージョンで使用されているstd::runtime_errorstd::logic_error。標準で定義されているものとは別に、MSVSのバージョンには、<exception>さらに2つのコンストラクターが含まれ(const char * const &)てい(const char * const &, int)ます。これらは、プライベート変数を設定するために使用されますconst char * _Mywhat。ifの場合_Mywhat != nullptrwhat()デフォルトでそれを返します。それに依存するコードはおそらく移植性がありません。
ジャスティン時間-モニカ

回答:


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
omg私はこのようなことをする方法を探していました。しかし、おそらく演算子>>を明示的な関数に変更して、(演算子の過負荷)を防止します
RomanPlášilMar

3
これとstd :: stringstreamの違いは何ですか?文字列ストリームが含まれているように見えますが、(私の知る限り)追加の機能はありません。
matts1 2016

2
一般に、100%安全な方法ではありません。std :: stringstreamメソッドは例外をスローする可能性があります。問題は次のとおり
Arthur P. Golubev

1
@ ArthurP.Golubevしかし、この場合、Formatter()インスタンスは、舞台裏で文字列ストリームをインスタンス化しますが、これも例外をスローする可能性があります。違いは何ですか?
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はこのように構築できないことに注意してください。具体的な派生クラスの1つを使用する必要があります。


27

など、さまざまな例外がありますがruntime_errorrange_erroroverflow_errorlogic_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

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必要に応じて、1つまたは他の引数をに変換します)。
マイクシーモア

9
@MikeSeymourはい。ただし、文字列を途中に配置し、数値を特定の精度でフォーマットする必要がある場合などは、より醜くなります。明確さの点で、古き良きフォーマット文字列を打ち負かすことは困難です。
Maxim Egorushkin 2012

2
@MikeSeymour私が投稿したコードがその時代を先取りしていることに同意するかもしれません。printfC ++ 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 ++はコンパイルします。
ヨッヘン

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::stringか、std::stringstreamまたは例外を引き起こす可能性が他の方法

一般に、例外のコンストラクターでメモリを割り当てる(C ++の方法で文字列を処理する)かstd::bad_alloc、スローする直前に違いはありません。例外は、本当に必要なものの前にスローできます。

したがって、スタックに割り当てられたバッファ(マキシムの答えのように)はより安全な方法です。

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とstringはr値の一時的なものであるため、この行が終了するとスコープ外になります。例外オブジェクトのコンストラクターは、コピーまたは(より良い)移動セマンティクスを使用して入力文字列を取得する必要があります。

追加:私はこのアプローチを「ベストプラクティス」とは必ずしも考えていませんが、機能し、ピンチで使用できます。最大の問題の1つは、このメソッドにはヒープの割り当てが必要なため、演算子<<がスローできることです。あなたはおそらくそのようなことを望まないでしょう。ただし、その状態になると、おそらくさらに多くの問題が発生します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.