C ++コールバックと例外

はじめに


ご存知のように、多くのCライブラリはコールバックを使用して機能を提供します。 これは、たとえば、SAXモデルを実装するためのexpatライブラリによって行われます。 コールバックまたはコールバックを使用して、ライブラリ側でカスタムコードを実行できます。 これまでのところ、このようなコードには副作用はありません。すべては問題ありませんが、C ++がアリーナに登場するとすぐに、すべてが重要になります。


そのため、C ++コードは、Cコードが準備されていない例外をスローする可能性があり、結局、コールバック関数はライブラリのコンテキストで実行されます。 そして、そのような関数からの出口が例外を投げることによって実行される場合、それはさらに進んでスタックを破壊します。
明らかな解決策は、この問題に最初に対処し始めたときに最も頻繁に出会ったものですが、そのようなコールバックで例外を完全に放棄することです。 しかし、これはもちろん、私たちの場合ではありません。

ケース


正当な理由で駐在員の例を挙げました。 このライブラリを使用すると、例外の問題が正確に発生しました。 タスクは次のとおりです。


expatのラッパーの開発に取り掛かりました。これは、以前のライブラリのインターフェイスを正確に繰り返しました。 このアイデアが生まれたのは、この問題を解決する過程にありました。

アイデア


実行がC ++コードに戻るまで、何らかの方法で例外のスローを延期する必要があります。 唯一の方法は、例外をどこかに保存し、安全な場所に再度スローすることです。 しかし、実装には多くの困難が伴います。

実装


クラスexception_storage


最初に、スローされた例外用のリポジトリを実装する必要があります。これは非常にシンプルに見えます。
class exception_storage
{
public :
inline ~exception_storage() {}
inline exception_storage() {}

void rethrow() const ;

inline void store(clone_base const & exc);
inline bool thrown() const ;
private :
std::auto_ptr< const clone_base> storedExc_;
};


clone_baseクラスはboost :: exception_detail :: clone_baseは私たちの目的に非常に適しています。
実装も複雑ではありません。

inline
void exception_storage::store(clone_base const & exc)
{
storedExc_.reset(exc.clone());
}
inline
bool exception_storage::thrown() const
{
return storedExc_.get() != 0;
}
inline
void
exception_storage::rethrow()
{
class exception_releaser
{
public :
inline
exception_releaser(std::auto_ptr< const clone_base> & excRef)
: excRef_(excRef)
{}
inline
~exception_releaser()
{
excRef_.reset();
}
private :
std::auto_ptr< const clone_base> & excRef_;
};

if (thrown())
{
volatile exception_releaser dummy(storedExc_);
storedExc_->rethrow();
}
}


3つのメソッド、store-clone_base :: cloneを使用して現在の例外を保存します。再スローは、発生した場合、最後の例外を再度スローします-例外が通知されたかどうか。 関数clone_base :: cloneは純粋に仮想であり、例外に使用する機能はboost :: enable_current_exceptionを提供し、1つの石で2羽の鳥を殺します。 。
また、rethrowメソッドの実装についても話したいと思います。 仮想関数の再スローがその中で呼び出されます(例外を使用して正確に生成するものの概念を持つのはこの関数です)。 exception_releaserクラスは、スローされたときに、保存された例外のメモリをクリアします。 生成中の例外はコピーされるため、storedExc_が指すオブジェクトは不要になります。 さらに、これによりブールフラグを保存して、生成の必要性を示すことができました。

例外フィルター


私の意見では、このシステム全体で最も難しいのは例外フィルターです。 素晴らしい後押し:: mplライブラリは、その実装に大いに役立ちます。 もっと詳しく考えてみましょう。

明らかに、例外をリポジトリに書き込むには、まずそれをキャッチする必要があります。 フィルターは、この問題だけを解決するように設計されています。 架空のコールバックを取得します。

void callback( void *userData, char const *libText);


例外がこの関数の範囲外に飛ぶのを防ぐ方法は?
最初の解決策は、すべてを傍受することです。 しかし、ここで私たちは言語の迷惑な制限に遭遇します。 catch(...)ブロック内の例外のタイプに関する情報を移植可能な方法で取得することはできません。 そのとき、この機能を実行できるという考えが浮かびました。 ライブラリユーザーがこれらのコールバックでキャッチする例外を指定できるようにします。
まず、コンパイル時の拡張可能な例外フィルターを実装しました。 次のようになります。

template < typename List, size_t i = boost::mpl::size<List>::value - 1>
class exception_filter
{
typedef typename boost::mpl::at_c<List, i>::type exception_type;
public :
static inline void try_(exception_storage & storage)
{
try
{
exception_filter<List, i - 1>::try_(storage);
}
catch (exception_type const & e)
{
storage.store(boost::enable_current_exception(e));
}
}
};
template < typename List>
class exception_filter<List, 0>
{
typedef typename boost::mpl::at_c<List, 0>::type exception_type;
public :
static inline void try_(exception_storage & storage)
{
try
{
throw ;
}
catch (exception_type const & e)
{
storage.store(boost::enable_current_exception(e));
}
}
};


これは、リストパラメータとしてboost :: mpl :: vectorを使用する、再帰的にインスタンス化されたテンプレートです。 ベクターでは、キャッチする例外のタイプを指定できます。 インスタンス化されると、テンプレートは次のように展開されます(擬似コード):

try
{
try
{
try
{
throw ;
}
catch (exception_type_1)
{
}
}
catch (exception_type_2)
{
}
}
catch (exception_type_N)
{
}


Nはmpl :: vectorの型の数です。
フィルターが例外を認識する場合、boost :: enable_current_exceptionを使用してexception_storageに保存されます。 smart_filter関数はテンプレートを制御します。 さらに、タイプリストで指定されていない他のすべての例外をキャッチします。

template <typename List>
inline void smart_filter(exception_storage & storage)
{
try
{
exception_filter<List>::try_(storage);
}
catch (...)
{
storage.store(boost::enable_current_exception(std::runtime_error("unexpected")));
}
}


まとめ


次に、これをCライブラリのラッパークラスに結合します。 アイデアを示すために、概略的に示します。

template < typename List>
class MyLib
{
public :
MyLib()
{
pureCLibRegisterCallback(&MyLib::callback);
pureCLibSetUserData( this );
}
virtual void doIt()
{
pureCLibDoIt();
storage_.rethrow();
}
protected :
virtual void callback( char const *) = 0;
private :
static void callback( void * userData, char const * libText)
{
if (MyLib<List> * this_ = static_cast <MyLib<List>*>(userData))
{
try
{
this_->callback(libText);
}
catch (...)
{
smart_filter<List>(this_->storage_);
}
}
}
exception_storage storage_;
};


それだけです。 次に、小さな使用例を示します。

struct MyClass
: public MyLib <
boost::mpl::vector
<
std::runtime_error,
std::logic_error,
std::exception
>
>
{
private :
void callback( char const * text)
{
throw std::runtime_error(text);
}
};
int main()
{
MyClass my;

try
{
my.doIt();
}
catch (std::exception const & e)
{
std::cout << e.what() << std::endl;
}
return 0;
}


この実装は、シングルスレッド環境を対象としています。

Source: https://habr.com/ru/post/J100623/


All Articles