CPP05 1.0
読み取り中…
検索中…
一致する文字列を見つけられません
レビューのノート

仮想関数のオーバーライドと例外仕様

throw()例外仕様の意義と仮想関数のオーバーライド

throw() が必要になる理由は、 「基底クラスの仮想関数をオーバーライド(上書き)するときのルール」 にあります。

1. throw() の正体:動的例外仕様

‍まず、throw() という記述そのものは、C++98/03における 動的例外仕様 (dynamic exception specification) と呼ばれるものです。

virtual const char *what() const throw();

この末尾の throw() は、 「この what という関数は、いかなる例外も投げないことをコンパイラとプログラマに約束します」 という意味になります。これは、関数がどのような例外を投げる可能性があるかを明示するための機能でした。

2. なぜ std::exception::what() のオーバーライドに必要なのか?

‍ここが最も重要なポイントです。C++には、仮想関数をオーバーライドする際に、この例外仕様に関する厳格なルールがあります。

ルール:派生クラスで仮想関数をオーバーライドする場合、その関数の例外仕様は、基底クラスの例外仕様よりも緩くしてはならない(同じか、より厳しくなければならない)。

std::exception クラスの what() メソッドは、C++98の標準規格で以下のように宣言されています。

// <exception> ヘッダ内の宣言(簡略化)
namespace std {
class exception {
public:
exception() throw();
virtual ~exception() throw();
virtual const char *what() const throw(); // <-- ここに throw() がある
};
}
T what(T... args)

基底クラスである std::exceptionwhat() が「例外を投げない ( throw() )」と約束しているため、それを継承してオーバーライドする GradeTooHighException::what() も、同じく「例外を投げない」という約束を守らなければなりません。

もし派生クラスで throw() を付けないと、基底クラスの約束を破ることになるため、コンパイラがエラーとして検出します。

public:
// 基底クラスが throw() なので、こちらも throw() が必須
virtual const char* what() const throw();
};
Exception thrown when a bureaucrat's grade is too high.

3. 継承しない場合との比較

‍一方、ご提示の2つ目の例のように、 std::exception を継承しない自作のクラスでは、このルールは適用されません。

class MyOwnException {
public:
// 比較対象となる基底クラスの仮想関数がないため、
// throw() を付けるも付けないも自由。
virtual const char *msg();
};

この msg() メソッドは、どのクラスからも継承しておらず、オーバーライドのルールに縛られないため、 throw() を付ける必要がないのです。

**catch ブロックの補足**

ちなみに、2つ目の例の catch ブロックの書き方は、構文として正しくありません。 catch 節では型を指定する必要があるため、正しくは以下のようになります。

// 正しいcatchブロックの書き方
try {
// ...
} catch (const MyOwnException& e) { // オブジェクトの型でキャッチする
std::cerr << "Caught exception: " << e.msg() << std::endl;
}
T endl(T... args)

4. 現代のC++ (noexcept) との関連

‍この 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++の重要な設計原則の一つです。