ライブラリ用にC++で例外階層を設計しています。「階層」は、std::runtime_errorから派生した4つのクラスです。例外クラスのスライスの問題を回避したいので、コピーコンストラクターを保護しました。しかし、どうやらgccはそれらのインスタンスをスローするときにコピーコンストラクターを呼び出す必要があるので、保護されたコピーコンストラクターについて文句を言います。Visual C ++ 8.0は、同じコードを正常にコンパイルします。例外クラスのスライスの問題を解決するための移植可能な方法はありますか?標準は、実装がスローされるクラスのコピーコンストラクターを必要とするかどうかについて何かを述べていますか?
5 に答える
例外には、パブリックコピーコンストラクタが必要です。例外処理を機能させるには、コンパイラがそれをコピーできる必要があります。
あなたの問題の解決策は、代わりに常に参照によってキャッチすることです:
try {
// some code...
throw MyException("lp0 is on fire!");
} catch (MyException const &ex) {
// handle exception
}
(const
-nessはオプションですが、例外オブジェクトを変更する必要がほとんどないため、常に入力します。)
トーマスの答えは正しいですが、「例外階層を設計する」ことで時間を無駄にしないこともお勧めします。クラス階層を設計することは、特に C++ 標準例外クラスから新しい例外タイプをいくつか (それ以上) 派生させることができる場合は特に、特に悪い考えです。
あなたのライブラリとは異なる例外階層を設計することは避けたいと思います。std::exception
可能な限り階層を使用し、常にその階層内の何かから例外を派生させます。Marshall Cline の C++ FAQ の例外部分を読みたいと思うかもしれません-特にFAQ 17.6、17.9、17.10、および17.12を読んでください。
「ユーザーに参照によるキャッチを強制する」に関しては、私はそれを行う良い方法を知りません。私が 1 時間ほどのプレイ (日曜日の午後) で思いついた唯一の方法は、ポリモーフィック スローに基づいています。
class foo_exception {
public:
explicit foo_exception(std::string msg_): m_msg(msg_) {}
virtual ~foo_exception() {}
virtual void raise() { throw *this; }
virtual std::string const& msg() const { return m_msg; }
protected:
foo_exception(foo_exception const& other): m_msg(other.m_msg) {}
private:
std::string m_msg;
};
class bar_exception: public foo_exception {
public:
explicit bar_exception(std::string msg_):
foo_exception(msg_), m_error_number(errno) {}
virtual void raise() { throw *this; }
int error_number() const { return m_error_number; }
protected:
bar_exception(bar_exception const& other):
foo_exception(other), m_error_number(other.m_error_number) {}
private:
int m_error_number;
};
Class(args).raise()
アイデアは、コピー コンストラクターを保護し、ユーザーにの代わりに呼び出すように強制することですthrow Class(args)
。これにより、ユーザーが参照によってのみキャッチできるポリモーフィックにバインドされた例外をスローできます。値でキャッチしようとすると、適切なコンパイラ警告が表示されます。何かのようなもの:
foo.cpp:59: エラー: 'bar_exception::bar_exception(const bar_exception&)' は保護されています
foo.cpp:103: エラー: このコンテキスト内
もちろん、throw
明示的に使用できなくなったり、同様のコンパイラ警告が表示されたりするため、これには代償が伴います。
foo.cpp: 関数 'void h()' 内:
foo.cpp:31: エラー: 'foo_exception::foo_exception(const foo_exception&)' は保護されています
foo.cpp:93: エラー: このコンテキスト内
foo.cpp:31: エラー: 'foo_exception::foo_exception(const foo_exception&)' は保護されています
foo.cpp:93: エラー: このコンテキスト内
全体として、参照によって常にキャッチする必要があることを示すコーディング標準とドキュメントに依存します。throw Class(constructorArgs)
ライブラリが参照によって処理する例外をキャッチし、新しいオブジェクト (または など)をスローするようにしてくださいthrow;
。他の C++ プログラマーも同じ知識を持っていると思いますが、念のためにドキュメントにメモを追加してください。
ライブラリのクライアントが例外を誤って値でキャッチするのを防ぐために私が見つけた2つの移植可能な方法は次のとおりです。
- 例外クラスのvirtual raiseメソッド内から例外をスローし、コピー コンストラクターを保護します。(ありがとうD.ショーリー)
- ライブラリから派生した例外をスローし、クライアントがキャッチする例外基本クラスを公開します。基本クラスは保護されたコピー コンストラクターを持つことができますが、これはそれらをキャッチする適切な方法のみを許可します。(同様の質問についてはこちらで言及されています)
C++ 標準では、スローの時点でコピー コンストラクターにアクセスできる必要があると規定されています。私の構成の Visual C++ 8.0 は、コピー コンストラクターの存在を強制しないため、標準のこの部分に違反していました。セクション15.1.3:
throw 式は、一時オブジェクトを初期化します。その型は、throw のオペランドの静的型から最上位の cv 修飾子を削除し、型を「T の配列」または「T を返す関数」から次のように調整することによって決定されます。それぞれ、「T へのポインター」または「T を返す関数へのポインター」です。
一時オブジェクトの使用に関連するコンストラクタとデストラクタの実行を除いて、プログラムの意味を変更せずに一時オブジェクトの使用を排除できる場合 (12.2)、ハンドラ内の例外を引数で直接初期化できます。投げ式の。スローされたオブジェクトがクラス オブジェクトであり、一時コピーを初期化するために使用されるコピー コンストラクターにアクセスできない場合、プログラムの形式は正しくありません (一時オブジェクトを別の方法で削除できる場合でも)。
この回答はOPによって質問に投稿されました。質問から削除し、別の回答として投稿しました。
組み込みの C++ 例外コードは使用しないでください。例外が必要な場合は、独自の例外を最初から作成してください。それが、同様の方法で実装されることは言うまでもなく、同様に動作することを確認する唯一の方法であり、率直に言って、C++での例外の実装は無能です。