プロが教えるわが家の防犯対策術!

曖昧な記憶なのですが、コントラクタで例外を発生させてはいけないというような記述を見たことがあります。

質問なのですが、
1.そもそも本当にコントラクタは例外を発生させてはいけないのか?
2.なぜコントラクタの例外は問題となるのか?
3.newなどの関数は例外を発生させるが、コントラクタ内でキャッチすれば問題ないのか?

以上、お願いします。

A 回答 (12件中1~10件)

http://www.cmagazine.jp/src/kinjite/cpp/exceptio …
と、いうことだそうです。
一言で言うと
「デストラクタが起動しない」からですね。
デストラクタでメモリの開放などを行っているとリークしてしまいます。
    • good
    • 0
この回答へのお礼

回答ありがとうございます。
つまり、コンストラクタで例外が発生すると、デストラクタが呼び出されず、
デストラクタで開放を行うものがリークするというわけですね?

対応としては、コンストラクタ内でキャッチして適切に処理すれば良いのでしょうか?

たとえば、newが失敗するなど、その時点でオブジェクトが死に体となったら、try-catchでそれ以前のメモリを開放し、再度throwするなどの対応は問題ないのでしょうか?

お礼日時:2006/09/07 17:26

できると思います。


ただ、インスタンスが生成されていないのでデストラクタが働かない。
なので、例外を投げる前にメモリ確保したものをきちんと開放してやらないとリークになるから気をつけろって話だったような気がします。
    • good
    • 0

コンストラクタで例外を発生させてはいけないというのは嘘です。

そもそも、オブジェクトの生成に失敗したことを通知するには、返却値のないコンストラクタの場合は例外を送出する以外にありません(errnoのような方法もあるにはありますが...)。

コンストラクタでの例外発生にまつわる問題を回避する最も簡単な方法は、new演算子(または似たような方法でのリソース確保)を複数行わないようにすることです。

A::A()
try
 : p1(new B), p2(new C)
{
}
catch (std::bad_alloc& e)
{
}

のようにしても、例外はcatchできますが、果たしてp1で例外が発生したのか、p2で例外が発生したのか知るすべがありません。
これだけの問題であれば、auto_ptrを使えば逃げられますが、例外安全に対して理解の浅いプログラマの場合は、ついつい信頼性の低いコードを書いてしまいがちです。

誤解のないように念を押しますが、コンストラクタの内部で発生したエラーの通知には例外を使用すべきです。しかし、同時に例外安全に対する認識と、具体的な対応方法を身に付けることが必須です。
    • good
    • 0
この回答へのお礼

回答ありがとうございます。
つまり、デストラクタが動かないことを前提にリークに気をつければ、コンストラクタが例外を投げても問題ないということですか?

お礼日時:2006/09/07 22:30

> デストラクタが動かないことを前提にリークに気をつければ、コンストラクタが例外を投げても問題ないということですか?



実際にはリークだけの話ではありません。
引数としてポインタや参照が渡された場合、途中で例外が発生しても、それらの内容を(できれば)破壊すべきではありませんし、静的データメンバ等の状態を変化にも注意しないと、その後の整合性が保てなくなります。
    • good
    • 0

> コンストラクタが例外を投げても問題ないということですか?



御意。既に回答があるように、それが本来の正しい(?)用法です。
ただし、例外に対する正しい理解をもつ開発者は少ないです。

 C言語にはこのような例外がありませんでしたので、
 例えば手探りで移行した人で知識が抜けていたりとか、

 C++の入門書でも説明が少なくて知らない人が増えたりとか、

 bad_allocすら投げない、try blockも使えない、
 でも有名な某コンパイラが幅を利かせたりとかもありますし、

 Cでは安全だったコードがC++では例外脆弱だったりとか、
 違う言語なので当たり前なのですが認識の無い人も多かったりするのが現実。

 Java/C#等の類似言語も、そもそもデストラクタが無かったりして
 C++の例外に対する誤った認識を与えてしまうことが多々あります。
 # 「尖がったC++」と比べて少し丸い言語たちですから、
 # これらの言語自体はそれでいいのですが、C++で同じ感覚だとは問題が…。

コンストラクタに限らず、例外を投げただけで呼びもとの処理が例外脆弱だと問題を起こす可能性があり、このことについてもある程度の配慮は必要であるとは思います。


なお、デストラクタでは例外を発生させてはいけません。
例外安全性が保てなくなります。
曖昧な記憶とのことですので、これと混同されている可能性はあるかと思います。
    • good
    • 0
この回答へのお礼

回答ありがとうございます。
私は、たとえばnewで言うなら

try{
new char[0x7fffffff]; // ここで例外が発生
}
catch(std::bad_alloc val){
// 例外で影響を受けるものの修復等
throw; // これで関数外まで例外を飛ばす
}

というような処理をよくやります。
このような処理は、コンストラクタに入れて大丈夫なのでしょうか?
また、このように例外が発生することを前提とするコードでもデストラクタには入れられないのでしょうか?

お礼日時:2006/09/08 00:59

> このような処理は、コンストラクタに入れて大丈夫なのでしょうか?



コンストラクタ内での例外の場合でも、通常の関数内処理は
> // 例外で影響を受けるものの修復等
この部分がキチンとなっていれば大丈夫です。
デストラクタが無い前提で、その時点までの状態をロールバックする。(面倒ですが…。既に指摘があるようにauto_ptrとかは役に立ちます)

コンストラクタで特に注意が必要なのは初期化指定で、#3の方が書かれているようなtry blockが必要です。
# これに対応してないコンパイラとかもまだ出回ってます。

ちなみに、初期化処理の場合、
 : p1(new B), p2(new C)
と書いてあっても、この順番に初期化されるわけではないとか(記述順ではなく、クラスでの定義順に依存する)、はまりどころも満載です。

*** *** ***

> また、このように例外が発生することを前提とするコードでも
> デストラクタには入れられないのでしょうか?

デストラクタ内部で例外が出るのは大丈夫ですが、外に投げたらいけません。

 ここでいう「いけません」は、言語レベルでの禁止ではありません。
 やることは可能ですが、例えば以下のような危険をはらみます。

例外処理中に例外がおきるとterminateされてしまうので、例外発生時のロールバック中にデストラクタが例外を出すと、プログラムが死にます(terminateのデフォルト処理はabort)。
そして、catchもできません。

なので、とりあえず、
> throw; // これで関数外まで例外を飛ばす
これは、uncaught_exceptionで判定してから再投入を決めるか、そもそも「投げない方が無難」です。
# uncaught_exceptionも同様に、まともに動かないコンパイラがまだ出てます。


このようなこともあり、例えばSTL等では例外を投げないことを要求されたりもします。
    • good
    • 0

デストラクタでの例外についてもついでにコメントしておくと...



ごくごく限られた状況でしか使用しない(というか使用状況を強制するように設計された)クラスであれば、デストラクタから例外を送出しても問題ないとは思います。
具体的には、関数内で定義されたローカルクラスで、二重例外やコンテナの要素になる可能性を完全に排除できている場合などです。

ちなみに、デストラクタから例外を送出してはいけないからといって、単に例外をcatchして捨てるだけの行為は凶悪ですのでやめましょう。
    • good
    • 0

御意>#7さん



設計上、そもそも投げない仕様か、投げても安全と保証できる用法なら問題はないです。

このような保証が得られない場合に、
uncaught_exception()が入っている方がterminateハンドラよりはマシなんじゃないかと個人的には思いますが、
結局のところは、ここで止めた例外の行き場が問題になる可能性があります。

例えば、例外中に例外が発生するケースだと、
最初の例外に起因しただけで最初の例外さえ正しく処理すれば後の例外は安全に無視できる、などという場合もありえますが、
後の例外が無条件に例外を投げるとterminateのグローバルハンドラで判断するしかなくなってしまいますので、uncaught_exceptionは有効だと思います。
# コンパイラが"正しく"対応していれば。

そうでないような場合(例外は投げるが呼び出し条件の保証もなく、例外中に例外が発生すると不正)が多いことも考えると、
一般則としては「デストラクタで例外を投げてはいけません」に落ち着くと思ってます。
    • good
    • 0

デストラクタで止めた例外の行き場に関しては、ひとつだけ裏技的な解法があります。

それは、異なるタスクに例外を通知し、そこで例外を処理させる方法です。
もちろん、ここでいう例外の通知方法も処理方法も、C++が言語レベルでサポートする監視ブロックとは異なるものです。

もっとも、そこまでやるのはよほどの状況ですし、ほぼすべての場合でデストラクタからの例外送出は禁止すべきですね。デストラクタからの例外送出は、gotoの1万倍は凶悪です。

最初の質問に戻ると、デストラクタからの例外とは違って、(馬鹿なコンパイラさえ使わなければ)コンストラクタからの例外送出は至って健全です。
    • good
    • 0
この回答へのお礼

回答ありがとうございます。
ここまでの意見を見ますと、
1.コンストラクタでの例外はリークやデータの破損に気をつけて使用できる。
2.デストラクタでの例外をオブジェクトの外側に投げることはC++言語の設計上ほとんど不可能。
3.デストラクタでの例外でも内部でcatchすれば問題ない。
4.STLを使用するなら例外を投げない方が無難。
と、言うことですね。

お礼日時:2006/09/11 21:47

以下の2点に関して、もしかすると誤解されている可能性があるので、念のため再確認します。



> 3.デストラクタでの例外でも内部でcatchすれば問題ない。

デストラクタ内部で例外をcatchしても、それだけでは臭いものに蓋をしただけで解決にはなりません。
原則として、デストラクタでは例外を送出しないようにしましょう。

> 4.STLを使用するなら例外を投げない方が無難。

STLというか、標準C++ライブラリや、多くのまともなライブラリは、ユーザー定義クラスのデストラクタから例外が送出されないことを前提にしています。
例外を投げない方が無難なのではなく、「デストラクタから」例外を送出してはいけないということです。
    • good
    • 0
この回答へのお礼

回答ありがとうございます。
>> 3.デストラクタでの例外でも内部でcatchすれば問題ない。
たしかに、臭いものに蓋的な考え方ですが、関数やLibraryから例外を投げられることもあるので、その場合はcatchして適切な処理を与えましょう。みたいな意味合いで書きました。

>> 4.STLを使用するなら例外を投げない方が無難。
コンストラクタは例外を投げない方が良い。
デストラクタは例外を投げてはいけない。
メソッドは例外を投げない方が無難。
ということですか?

お礼日時:2006/09/12 01:35

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!