「More C++ Idioms/多態的例外(Polymorphic Exception)」の版間の差分

削除された内容 追加された内容
Yak! (トーク | 投稿記録)
現内容を翻訳。
1 行
=<center>多態的例外(Polymorphic Exception)</center>=
=== Intent意図 ===
* 例外オブジェクトを多態的に作成する。
* To create an exception object polymorphically
* 送出されるかも知れない例外の具体的な詳細からモジュールを分離する。
* To decouple a module from the concrete details of the exceptions it may throw
 
=== Also Known As別名 ===
 
=== Motivation動機 ===
よく知られたオブジェクト指向ソフトウェア設計のガイドラインである[http://en.wikipedia.org/wiki/Dependency_inversion_principle 依存関係逆転の原則(Dependency Inversion Principle (DIP))]によれば、より高位のモジュールはより低位のモジュールに直接依存するべきではない。代わりに、双方とも(しっかりと定義されたインタフェースの形で取り込まれた)共通の抽象に依存するべきである。例えば、ある ''Person'' 型のオブジェクト(John とする) は ''HondaCivic'' 型のオブジェクトを作成したり使うべきではない。代わりに、John は単純に ''Car'' インタフェース、''HondaCivic'' の抽象基本クラスに委ねるべきである。これにより、John は将来簡単に ''Person'' クラスを変更することなく ''Corvette'' にアップグレードする事が出来る。John は[http://en.wikipedia.org/wiki/Dependency_injection 依存性注入(dependency injection)]技法を使って(任意の)車の具体的なインスタンスによって設定する事が出来る。DIP を用いる事で、簡単にユニットテストでき自由度が高く拡張可能なモジュールとなる。依存性注入によって実際のオブジェクトを簡単にモックオブジェクトに置き換えられる為、ユニットテストは DIP によって単純化される。
[http://en.wikipedia.org/wiki/Dependency_inversion_principle Dependency Inversion Principle (DIP)], a popular object-oriented software design guideline states that higher level modules should not depend directly on lower level modules. Instead, both should depend on common abstractions (captured in the form of well-defined interfaces). For example, an object of type ''Person'' (say John) should not create and use an object of type ''HondaCivic'' but instead John should simply commit to a ''Car'' interface, which is an abstract base class of ''HondaCivic''. This allows John to upgrade to a ''Corvette'' easily in future without any changes to the class ''Person''. John can be "configured" with a concrete instance of a car (any car) using [http://en.wikipedia.org/wiki/Dependency_injection dependency injection] technique. Use of DIP leads to flexible and extensible modules that are easy to unit test. Unit testing is simplified by DIP because real objects can be easily replaced with mock objects using dependency injection.
 
しかし、DIP が侵害されているタイミングもある。1 つは Singleton パターンの使用時であり、もう一つは例外の送出時である。Singleton パターンは静的な ''instance()'' 関数にアクセスする際、具体的なクラス名の使用を強制する為 DIP に違反する。Singleton は関数やコンストラクタを呼び出す際パラメータとして渡されるべきである。似たような状況が C++ で例外を取り扱う際に発生する。C++ の ''throw'' 節は例外を送出する為に具体的な型名(クラス)を必要とする。例えば、
However, there are several occasions when DIP is violated: (1) while using the Singleton pattern and (2) while throwing exceptions! Singleton pattern breaks DIP because it forces the use of the concrete class name while accessing the static ''instance()'' function. A singleton should be passed as a parameter while calling a function or a constructor. A similar situation arises while dealing with exceptions in C++. The ''throw'' clause in C++ requires a concrete type name (class) to raise an exception. For example,
 
<source lang="cpp">
15 行
</source>
 
このように例外を送出するモジュールは直ちに DIP の違反となる。もちろん、実際の例外オブジェクトを簡単にモックの例外オブジェクトに置き換えられない為、そのようなモジュールではユニットテストはより困難になる。下記のような解法は不幸にも失敗する。なぜなら C++ の ''throw'' は静的な型付けを使用し多態性について何も考慮しないからである。
Any module that throws exceptions like this immediately results into a violation of DIP. Naturally, it is harder to unit test such a module because real exception objects cannot easily be replaced with mock exception objects. A solution like the one below fails miserably as ''throw'' in C++ uses static typing and knows nothing about polymorphism.
 
<source lang="cpp">
23 行
void foo(ExceptionBase& e)
{
throw e; // Uses static type of例外送出時の e while rasing an exception.の静的な型が使用される。
}
int main (void)
32 行
}
catch (ExceptionDerived& e) {
// foo の中で送出された例外はこの catch には合致しない。
// Exception raised in foo does not match this catch.
}
catch (...) {
// foo の中で送出された例外はここで捕捉される。
// Exception raised in foo is caught here.
}
}
</source>
 
多態的例外(Polymorphic Exception)イディオムはこの問題に対処する。
Polymorphic exception idiom addresses the issue.
 
=== Solution and Sample Code解法とサンプルコード ===
多態的例外(Polymorphic Exception)イディオムは単純に、仮想関数 ''raise()'' を使うことで派生クラスに例外送出を委譲する。
Polymorphic exception idiom simply delegates the job of raising the exception back to the derived class using a virtual function ''raise()''
 
<source lang="cpp">
58 行
void foo(ExceptionBase& e)
{
e.raise(); // Uses dynamic type of例外送出時の e while raising an exception.の動的な型が使用される。
}
int main (void)
67 行
}
catch (ExceptionDerived& e) {
// foo の中で送出された例外は今やこの catch に合致する。
// Exception raised in foo now matches this catch.
}
catch (...) {
// もはやここには来ない!
// not here anymore!
}
}
</source>
 
The仮想関数中に ''throw'' statement has been moved into virtual functions. The文が移動している。関数 ''raisefoo'' function invoked in function中で呼び出される ''fooraise'' is polymorphic and selects the implementation in either 関数は多態的であり、''ExceptionBase'' or ''ExceptionDerived'' class depending upon what is passed as a parameter の実装がパラメータとして渡されたものによって選択される(dependency injection依存性注入). Type of ''*this'' is obviously known at compile-time, which results into raising a polymorphic exception. The structure of this idiom is very similar to that of の型はコンパイル時に明らかに分かる為、多態的な例外が送出される事になる。このイディオムの構造は[[More C++ Idioms/仮想コンストラクタ(Virtual Constructor)|仮想コンストラクタ(Virtual Constructor)]] idiom.イディオムに非常によく似ている。
 
'''多態的例外の伝播'''
'''Propagating a polymorphic exception'''
 
一つの例外が、プログラムやライブラリの異なる階層で異なったやり方で複数の catch 文で処理されることは非常に良くある。そのような場合、より外側の catch ブロックが(存在するならば)必要な処理を行えるように先行する catch ブロックは例外を再送する必要がある。多態的な例外が関係する場合、内側の catch ブロックは外側の catch ブロックに渡す前に例外オブジェクトを変更するかも知れない。そのような場合、確実にオリジナルの例外オブジェクトが伝播されるよう注意を払わねばならない。一見した所無害に見えるが失敗している以下のプログラムを考えてみよう。
Quite often an exception is handled in multiple catch statements to treat it differently in different layers of the program/library. In such cases, the earlier catch blocks need to rethrow the exception so that the outer catch blocks, if any, may take the necessary actions. When a polymorphic exception is involved, inner catch block(s) may modify the exception object before passing it on to the catch blocks up in the stack. In such cases, care must be taken to ensure that the original exception object is propagated. Consider a seemingly annocuous looking program below, which fails to do that.
 
<source lang="cpp">
try {
foo(e); // throws an instance of前述のように ExceptionDerived as before.クラスのインスタンスが送出される。
}
catch (ExceptionBase& e) // Note the base class. 基本クラスException is caught polymorphically.は多態的に捕捉される事に注意。
{
// 例外を処理する。オリジナルの例外オブジェクトを変更するかも知れない。
// Handle the exception. May modify the original exception object.
throw e; // 警告!オブジェクトのスライシングが発生している。
throw e; // Warning! Object slicing is underway.
}
</source>
 
文 ''throw e'' はオリジナルの例外オブジェクトを送出していない。代わりに、オリジナルのオブジェクトの一部分(ExceptionBase の部分)だけ切り出されたコピー(sliced copy)を送出する。なぜなら目の前にある例外の静的な型を考慮するからである。無言のうちに、派生された例外オブジェクトは失われるだけでなく基本型の例外オブジェクトに変換されてしまう。外側の catch ブロックでは、この catch が持っていたものと同じ情報にアクセスすることができない。この問題に対処する方法が二つある。
The ''throw e'' statement does not throw the original exception object. Instead, it throws a sliced copy (only ExceptionBase part) of the original object because it considers the static type of the expression in front of it. Silently, the derived exception object was not only lost but also translated into the base type exception object. The catch blocks up in the stack do not have access to the same information this catch had. There are two ways to address the problem.
 
* 単純に(いかなる式も続かない形で) ''throw'' を使う。これはオリジナルの例外オブジェクトを再送する。
* Simply use ''throw;'' (without any expression following it). It will rethrow the original exception object.
* 再度、多態的な例外イディオムを使用する。''raise()'' 仮想関数が ''throw *this'' を使うためオリジナルの例外オブジェクトのコピーが送出される。
* Use polymorphic exception idiom again. It will throw a copy of the original exception object because the ''raise()'' virtual function uses ''throw *this''.
 
 
<source lang="cpp">
try {
foo(e); // throws an instance of前述のように ExceptionDerived as before.クラスのインスタンスが送出される。
}
catch (ExceptionBase& e) // Note the base class. 基本クラスException is caught polymorphically.は多態的に捕捉される事に注意。
{
// 例外を処理する。オリジナルの例外オブジェクトを変更するかも知れない。
// Handle the exception. May modify the original exception object.
// 以下の二つの内いずれか一方のみを使用する。
// Use only one of the following two.
throw; // Option 1: Original derived exception is thrown.選択肢1:オリジナルの派生型例外が送出される。
e.raise(); // 選択肢2:オリジナルの派生型例外オブジェクトのコピーが送出される。
e.raise(); // Option 2: A copy of the original derived exception object is thrown.
}
</source>
 
=== Known Uses既知の利用 ===
 
=== Related Idioms関連するイディオム ===
[[More C++ Idioms/仮想コンストラクタ(Virtual Constructor)|仮想コンストラクタ(Virtual Constructor)]]
 
=== References ===
* [http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.10 How do I throw polymorphically? (parashift)]
* [http://www.cis.nctu.edu.tw/chinese/doc/research/c++/C++FAQ-English/exceptions.html#faq-17.10 How do I throw polymorphically? (NCTU)]
<noinclude>
[[en:More C++ Idioms/Polymorphic Exception]]
</noinclude>
[[Category:{{BASEPAGENAME}}|{{SUBPAGENAME}}]]