|
CPP05 1.0
|
throw()が必要になる理由は、 「基底クラスの仮想関数をオーバーライド(上書き)するときのルール」 にあります。
まず、
throw()という記述そのものは、C++98/03における 動的例外仕様 (dynamic exception specification) と呼ばれるものです。
virtual const char *what() const throw();この末尾の
throw()は、 「このwhatという関数は、いかなる例外も投げないことをコンパイラとプログラマに約束します」 という意味になります。これは、関数がどのような例外を投げる可能性があるかを明示するための機能でした。
ここが最も重要なポイントです。C++には、仮想関数をオーバーライドする際に、この例外仕様に関する厳格なルールがあります。
ルール:派生クラスで仮想関数をオーバーライドする場合、その関数の例外仕様は、基底クラスの例外仕様よりも緩くしてはならない(同じか、より厳しくなければならない)。
std::exceptionクラスのwhat()メソッドは、C++98の標準規格で以下のように宣言されています。// <exception> ヘッダ内の宣言(簡略化)namespace std {class exception {public:exception() throw();virtual ~exception() throw();};}T what(T... args)基底クラスである
std::exceptionのwhat()が「例外を投げない (throw())」と約束しているため、それを継承してオーバーライドするGradeTooHighException::what()も、同じく「例外を投げない」という約束を守らなければなりません。もし派生クラスで
throw()を付けないと、基底クラスの約束を破ることになるため、コンパイラがエラーとして検出します。public:// 基底クラスが throw() なので、こちらも throw() が必須virtual const char* what() const throw();};Exception thrown when a bureaucrat's grade is too high.
一方、ご提示の2つ目の例のように、
std::exceptionを継承しない自作のクラスでは、このルールは適用されません。class MyOwnException {public:// 比較対象となる基底クラスの仮想関数がないため、// throw() を付けるも付けないも自由。virtual const char *msg();};この
msg()メソッドは、どのクラスからも継承しておらず、オーバーライドのルールに縛られないため、throw()を付ける必要がないのです。**
catchブロックの補足**ちなみに、2つ目の例の
catchブロックの書き方は、構文として正しくありません。catch節では型を指定する必要があるため、正しくは以下のようになります。
この
throw()という動的例外仕様は、いくつかの問題点があったため、C++11で 非推奨(deprecated) となり、代わりによりシンプルで強力なnoexceptというキーワードが導入されました。現代のC++では、
std::exception::what()は以下のように宣言されています。// C++11以降の宣言virtual const char* what() const noexcept; // throw() が noexcept に変わったもしC++11以降の規格でコンパイルする場合、
throw()の代わりにnoexceptを使うのが正しい作法となります。// C++11以降で書く場合public:virtual const char \*what() const noexcept;};
throw()を付けなければならないのは、std::exceptionという基底クラスが持つwhat()メソッドの「例外を投げない」というルールを、オーバーライドする際に忠実に守る必要があるからです。これは、ポリモーフィズム(多態性)を安全に機能させるための、C++の重要な設計原則の一つです。