17

次の目的で、.NET フレームワークのものを模倣する Exception クラスを C++ で実装したいと思います (Java にも同様のものがあります)。

  1. 例外チェーン: 上位レベルでキャッチされた例外が下位レベルの例外をラップして「変換」し、これらの下位レベルの例外を何らかの形で (InnerExceptionこの場合はメンバー内で)保持する「例外変換」の概念を実装したいと思います。 . このため、上位レベルでスローされた各例外とともに内部例外を格納するメカニズムが必要です。InnerExceptionmember は、以下の実装でこれを提供します。

  2. 例外の継承:たとえば、、およびIoExceptionから派生することが可能であるべきです。これは些細なことのように思えますが、できれば RTTI や.ExceptionSerialPortExceptionIoExceptiontypeid

これは、可能にしたい例外処理ロジックのサンプルです。

try
{
    try
    {
        try
        {
            throw ThirdException(L"this should be ThirdException");
        }
        catch(Exception &ex)
        {
            throw SubException(L"this should be SubException", ex);
        }
    }
    catch(Exception &ex)
    {
        throw SubException(L"this should be SubException again", ex);
    }
}
catch(Exception &ex)
{
    throw Exception(L"and this should be Exception", ex);
}

そして、最上層で「最も外側の」例外をキャッチするときに、InnerExceptionメンバーを介して例外チェーン全体を解析およびフォーマットして、次のようなものを表示できるようにしたいと考えています。

例外チェーンのフォーマット

これまでのところ、次の実装を考え出しました。

ちょっとした注意: CStringMicrosoft 固有の文字列クラスです (Visual C++ に慣れていない人向けです)。

class Exception
{
protected:

    Exception(const Exception&) {};
    Exception& operator= (const Exception&) {};

public:

    Exception(const CString &message) : InnerException(0), Message(message) {}
    Exception(const CString &message, const Exception &innerException) : InnerException(innerException.Clone()), Message(message) {}

    virtual CString GetExceptionName() const { return L"Exception"; }

    virtual Exception *Clone() const
    {
        Exception *ex = new Exception(this->Message);
        ex->InnerException = this->InnerException ? this->InnerException->Clone() : 0;
        return ex;
    }

public:

    virtual ~Exception() { if (InnerException) delete InnerException; }

    CString Message;
    const Exception *InnerException;
};

さて、ここには何がありますか。protectedコピーを防ぐために、コピーコンストラクターと代入演算子が作成されます。各オブジェクトは内部の例外オブジェクトを「所有」する (そしてデストラクタでそれを削除する) ため、デフォルトの浅いコピーは受け入れられません。次に、2 つの非常に標準的なコンストラクタと、オブジェクトを削除する仮想デストラクタがありInnerExceptionます。Clone()仮想メソッドは、主に内部例外オブジェクトを格納するために、オブジェクトのディープ コピーを担当します (2 番目のコンストラクターを参照)。最後にGetExceptionName()、仮想メソッドは、例外クラス名を識別するための RTTI の安価な代替手段を提供します (これはクールに見えませんが、より良い解決策を思い付くことができませんでした。比較のために: .NET では単純に を使用できますsomeException.GetType().Name)。

これでうまくいきます。しかし...私はこのソリューションが1つの特定の理由で好きではありません:各派生クラスに必要なコーディングの量です。基本クラスの機能にまったく追加を提供しないクラスを派生させる必要があると考えてくださいSubException。それは、その使用法を区別するためにカスタム名 (「SubException」、「IoException」、「ProjectException」など) を提供するだけです。シナリオ。このような例外クラスごとに、ほぼ同じ量のコードを提供する必要があります。ここにあります:

class SubException : public Exception
{
protected:

    SubException(const SubException& source) : Exception(source) {};
    SubException& operator= (const SubException&) {};

public:

    SubException(const CString &message) : Exception(message) {};
    SubException(const CString &message, const Exception &innerException) : Exception(message, innerException) {};

    virtual CString GetExceptionName() const { return L"SubException"; }

    virtual Exception *Clone() const
    {
        SubException *ex = new SubException(this->Message);
        ex->InnerException = this->InnerException ? this->InnerException->Clone() : 0;
        return ex;
    }
};

protected毎回コピー コンストラクターと代入演算子を提供する必要があるという事実は好きではありません。Clone毎回メソッドを複製しなければならず、基本メンバーをコピーするコードも複製しなければならないという事実は好きではありません ( InnerException... )、単純に...これはエレガントなソリューションではないと思います。しかし、私はより良いものを考えることができませんでした. この概念を「適切に」実装する方法はありますか? それとも、これが C++ で可能なこの概念の最良の実装でしょうか? それとも、私はこれを完全に間違っていますか?

PS: C++11 (Boost にも) には、いくつかの新しい例外クラスを使用したこの目的 (例外チェーン) のためのメカニズムがいくつか存在することは知っていますが、主にカスタムの「古い C++ 互換」の方法に興味があります。さらに、誰かが同じことを実現する C++11 のコードを提供できれば、それは良いことです。

4

4 に答える 4

19

C++11 には既にnested_exceptionがあります。Boostcon/C++Next 2012 で、C++03 と C++11 の例外についての講演がありました。ビデオは YouTube にあります。

  1. http://www.youtube.com/watch?v=N9bR0ztmmEQ&feature=plcp
  2. http://www.youtube.com/watch?v=UiZfODgB-Oc&feature=plcp
于 2012-11-13T08:04:22.523 に答える
4

余分なコードがたくさんありますが、良いことは、クラスごとにまったく変わらない非常に簡単な追加コードであるため、マクロをプリプロセッサすることができるということです。

#define SUB_EXCEPTION(ClassName, BaseName) \
  class ClassName : public BaseName\
  {\
  protected:\
  \
      ClassName(const ClassName& source) : BaseName(source) {};\
      ClassName& operator= (const ClassName&) {};\
  \
  public:\
  \
      ClassName(const CString &message) : BaseName(message) {};\
      ClassName(const CString &message, const BaseName &innerException) : BaseName(message, innerException) {};\
  \
      virtual CString GetExceptionName() const { return L"ClassName"; }\
  \
      virtual BaseName *Clone() const\
      {\
          ClassName *ex = new ClassName(this->Message);\
          ex->InnerException = this->InnerException ? this->InnerException->Clone() : 0;\
          return ex;\
      }\
  };

次に、次のようにするだけで、さまざまなユーティリティ例外を定義できます。

SUB_EXCEPTION(IoException, Exception);
SUB_EXCEPTION(SerialPortException, IoException);
于 2012-11-13T08:05:23.560 に答える
2

数年前、私はこれを書きました: C++ での連鎖例外のチェーン解除

基本的に、元の例外をキャッチするのは難しいため、例外は相互にネストされませんが、例外がキャッチ ポイントに移動する間、別のメカニズムが例外によってアクセスされたすべての関数を追跡します。

その再訪バージョンは、Bitbucket のライブラリ Imebraあります

ここで、いくつかの改善を加えてそれを書き直します (たとえば、スタック トレースを保持するためにローカル スレッド ストレージを使用します)。

このアプローチを使用すると、スローされた元の例外をキャッチできますが、例外が catch ステートメントに戻る間に、スタック トレースと、場合によっては例外がアクセスした関数によって追加されたその他の情報を保持できます。

于 2012-11-15T08:10:24.013 に答える
2

boost::exception アプローチに従わないでください。Boost::exception はさまざまなユース ケースに対応しています。特に、コール スタックに分散された正確な例外コンテキストを収集する場合に役立ちます。次の例を検討してください。

#include "TSTException.hpp"

struct DerivedException: TST::Exception {};

int main() try
{
    try
    {
        try
        {
            try
            {
                throw std::runtime_error("initial exception");
            }
            catch(...)
            {
                throw TST::Exception("chaining without context info");
            }
        }
        catch(...)
        {
            TST_THROW("hello world" << '!');
        }
    }
    catch(...)
    {
        TST_THROW_EX(DerivedException, "another exception");
    }
}
catch(const TST::Exception& ex)
{
    cout << "diagnostics():\n" << ex;
}
catch(const std::exception& ex)
{
    cout << "what(): " << ex.what() << endl;
}

私が理解している「例外チェーン」ソリューションは、次のような出力を生成する必要があります。

$ ./test
diagnostics():
Exception: another exception raised from [function: int main() at main.cpp:220]
Exception: hello world! raised from [function: int main() at main.cpp:215]
Exception: chaining without context info raised from [function: unknown_function at unknown_file:0]
Exception: initial exception

ご覧のように、互いにチェーンされた例外があり、診断出力にはコンテキスト情報とオプションのスタック トレースを含むすべての例外が含まれます (コンパイラ/プラットフォームに依存するため、ここには示されていません)。「例外チェーン」は、新しい C++11 エラー処理機能 (std::current_exception または std::nested_exception) を使用して自然に実現できます。TSTException.hpp の実装は次のとおりです (ソース コードの追加はご容赦ください)。

#include <iostream>
#include <sstream>
#include <stdexcept>
#include <exception>
#include <vector>
#include <string>
#include <memory>
#include <boost/current_function.hpp>
#include <boost/foreach.hpp>

using namespace std;

namespace TST
{

class Exception: virtual public std::exception
{
public:
    class Context
    {
    public:
        Context():
            file_("unknown_file"),
            line_(0),
            function_("unknown_function")
        {}
        Context(const char* file, int line, const char* function):
            file_(file? file: "unknown_file"),
            line_(line),
            function_(function? function: "unknown_function")
        {}
        const char* file() const { return file_; }
        int line() const { return line_; }
        const char* function() const { return function_; }
    private:
        const char* file_;
        int line_;
        const char* function_;
    };
    typedef std::vector<std::string> Stacktrace;
    //...
    Exception()
    {
        initStacktraceAndNestedException();
    }
    explicit Exception(const std::string& message, const Context&& context = Context()):
        message_(message),
        context_(context)
    {
        message.c_str();
        initStacktraceAndNestedException();
    }
    ~Exception() throw() {}
    //...
    void setContext(const Context& context) { context_ = context; }
    void setMessage(const std::string& message) { (message_ = message).c_str(); }
    const char* what() const throw () { return message_.c_str(); }
    void diagnostics(std::ostream& os) const;
protected:
    const Context& context() const { return context_; }
    const std::exception_ptr& nested() const { return nested_; }
    const std::shared_ptr<Stacktrace>& stacktrace() const { return stacktrace_; }
    const std::string& message() const { return message_; }
private:
    void initStacktraceAndNestedException();
    void printStacktrace(std::ostream& os) const;
    std::string message_;
    Context context_;
    std::shared_ptr<Stacktrace> stacktrace_;
    std::exception_ptr nested_;
};

std::ostream& operator<<(std::ostream& os, const Exception& ex)
{
    ex.diagnostics(os);
    return os;
}

std::ostream& operator<<(std::ostream& os, const Exception::Context& context)
{
    return os << "[function: " << context.function()
              << " at " << context.file() << ':' << context.line() << ']';
}

void Exception::diagnostics(std::ostream& os) const
{
    os << "Exception: " << what() << " raised from " << context_ << '\n';
    if (const bool haveNestedException = nested_ != std::exception_ptr())
    {
        try
        {
            std::rethrow_exception(nested_);
        }
        catch(const TST::Exception& ex)
        {
            if(stacktrace_ && !ex.stacktrace())//if nested exception doesn't have stacktrace then we print what we have here
                    printStacktrace(os);
            os << ex;
        }
        catch(const std::exception& ex)
        {
            if(stacktrace_)
                printStacktrace(os);
            os << "Exception: " << ex.what() << '\n';
        }
        catch(...)
        {
            if(stacktrace_)
                printStacktrace(os);
            os << "Unknown exception\n";
        }
    }
    else if(stacktrace_)
    {
        printStacktrace(os);
    }
}

void Exception::printStacktrace(std::ostream& os) const
{
    if(!stacktrace_)
    {
        os << "No stack trace\n";
        return;
    }
    os << "Stack trace:";
    BOOST_FOREACH(const auto& frame, *stacktrace_)
    {
        os << '\n' << frame;
    }
    os << '\n';
}

void Exception::initStacktraceAndNestedException()
{
    nested_ = std::current_exception();
    if(const bool haveNestedException = nested_ != std::exception_ptr())
    {
        try
        {
            throw;
        }
        catch(const TST::Exception& ex)
        {
            if(ex.stacktrace())
            {
                stacktrace_ = ex.stacktrace();
                return;
            }
        }
        catch(...) {}
    }
    /*TODO: setStacktrace(...); */
}

}//namespace TST

#ifdef TST_THROW_EX_WITH_CONTEXT
#error "TST_THROW_EX_WITH_CONTEXT is already defined. Consider changing its name"
#endif /*TST_THROW_EX_WITH_CONTEXT*/

#define TST_THROW_EX_WITH_CONTEXT(                                      \
    CTX_FILE, CTX_LINE, CTX_FUNCTION, EXCEPTION, MESSAGE)               \
    do                                                                  \
    {                                                                   \
        EXCEPTION newEx;                                                \
        {                                                               \
            std::ostringstream strm;                                    \
            strm << MESSAGE;                                            \
            newEx.setMessage(strm.str());                               \
        }                                                               \
        newEx.setContext(                                               \
            TST::Exception::Context(                                    \
                CTX_FILE, CTX_LINE, CTX_FUNCTION));                     \
        throw newEx;                                                    \
    }                                                                   \
    while(0)

#ifdef TST_THROW_EX
#error "TST_THROW_EX is already defined. Consider changing its name"
#endif /*TST_THROW_EX*/

#define TST_THROW_EX(EXCEPTION, MESSAGE)                                       \
    TST_THROW_EX_WITH_CONTEXT(__FILE__, __LINE__, BOOST_CURRENT_FUNCTION, EXCEPTION, MESSAGE)

#ifdef TST_THROW
#error "TST_THROW is already defined. Consider changing its name"
#endif /*TST_THROW*/

#define TST_THROW(MESSAGE)                                              \
    TST_THROW_EX(TST::Exception, MESSAGE)

私は C++11 を部分的にサポートするコンパイラ (gcc 4.4.7) を使用しているので、古いスタイルのコードをここで見ることができます。参考までに、次のコンパイル パラメータを使用してこの例をビルドできます (-rdynamic はスタック トレース用です)。

g++ main.cpp TSTException.hpp -rdynamic -o test -std=c++0x

于 2014-02-03T06:32:03.073 に答える