- ベストアンサー
コンストラクタやデストラクタと例外について
対処法が「概念的に」書かれているサイトや書籍は結構あるようなのですが、具体的なコードでビシッと書かれてることや、そもそも根本的に「どういう場合に例外が発生するのか」の、帰納的な説明はなかなか見つからないので、もやもやしています。 また、「人が書いたコードを使う場合に全部読んでられないような状況」という前提が暗黙にあって書かれている文章が多いと感じます。 しかし、根本的なところが分からないともやもや感は拭えません。 1.もし仮に自分が全てのコードを把握し、「コーディングの段階で明白に消せる例外の可能性」を、あらかじめ消しておける(さすがにnewなどC++の標準的機能は使用し)とした場合は 自分自身で例外をthrowするか、メモリ不足でbad_allocになる以外にコンストラクタ(あるいはデストラクタ)でcatch出来るような例外が発生する可能性はあるのでしょうか? そして例えば以下のように書いたとします。(必要なヘッダ等は省略してあります) ///////////////////ヘッダ側//////////////////////// class A{ double d; public: A(double a=0) : d(a) {} ~A(void){} }; class B{ std::auto_ptr<A> p; A *alist[10]; public: B(void); ~B(void); }; ///////////////////こちらがソースです//////////////////////// B::B(void) try { memset(&alist,0,sizeof alist); std::auto_ptr<A> ap( new A(.8) ); p=ap; for ( int i=10;i; ) alist[--i]=new A; } catch(std::bad_alloc){ for ( int i=10;i; ) delete alist[--i]; } catch(...) { /*......*/ } B::~B(void){ try{ for ( int i=10;i; ) delete alist[--i]; }catch(...){} } こうしたとすると 2.このコードはコンストラクタ中で例外が発生した場合、コンストラクタなので「呼ばれることになった原因の場所へ自動的に例外が再度投げられる」という点を除けば、安全でしょうか? 3.そもそもコンストラクタで例外が発生した場合、呼び出し元に届くように「自動的に、あるいは手動で」投げなければ「ならない」のでしょうか? 4. 1とかぶりますが「このコードの場合に限定」していうと、catch(std::bad_alloc)の後ろに書いたように、果たしてcatch(...)は必要なのでしょうか? つまりstd::bad_alloc以外の例外が発生する可能性はありますか? 5.このコードの場合では、このようにデストラクタをtryブロックで囲う必要は事実上ないと考えて良いですか? 6.また、もしclass Aのデストラクタを見ることができないという場合は、逆に囲うべき、という意味なのでしょうか? 7.auto_ptrのかわりに通常のポインタを使う場合 ポインタの配列alistと同じように、先にNULLにしておいてからnewして、catch(std::bad_alloc)に入った場合は delete ポインタ。 例外が発生しなかった場合デストラクタのほうにも解放処理はちゃんと書く、としていれば、この場合大丈夫でしょうか?
- みんなの回答 (5)
- 専門家の回答
質問者が選んだベストアンサー
> 1.もし仮に自分が全てのコードを把握し、「コーディングの段階で明白に消せる例外の可能性」を、あらかじめ消しておける(さすがにnewなどC++の標準的機能は使用し)とした場合は > 自分自身で例外をthrowするか、メモリ不足でbad_allocになる以外にコンストラクタ(あるいはデストラクタ)でcatch出来るような例外が発生する可能性はあるのでしょうか? 例外が送出される可能性を確実に消すことができたのであれば、その可能性はありません。 > 2.このコードはコンストラクタ中で例外が発生した場合、コンストラクタなので「呼ばれることになった原因の場所へ自動的に例外が再度投げられる」という点を除けば、安全でしょうか? ポインタの配列をmemsetでゼロクリアし、各要素がNULLであることを期待しているようですが、それは規格上保証されませんので、安全とはいえません。 > 3.そもそもコンストラクタで例外が発生した場合、呼び出し元に届くように「自動的に、あるいは手動で」投げなければ「ならない」のでしょうか? コンストラクタで発生したエラーは、例外以外の通知手段が原則としてありません。 > 4. 1とかぶりますが「このコードの場合に限定」していうと、catch(std::bad_alloc)の後ろに書いたように、果たしてcatch(...)は必要なのでしょうか? > つまりstd::bad_alloc以外の例外が発生する可能性はありますか? 未定義の動作が原因で例外が送出されるのでなければ、その可能性はないと思います。 > 5.このコードの場合では、このようにデストラクタをtryブロックで囲う必要は事実上ないと考えて良いですか? > 6.また、もしclass Aのデストラクタを見ることができないという場合は、逆に囲うべき、という意味なのでしょうか? よほど特殊な状況を除けば、デストラクタから例外を送出してはなりません。また、例外を送出しないために、臭い物にフタをするだけでもだめです。 つまり、デストラクタでは決して失敗しないように設計する必要があります。 > 7.auto_ptrのかわりに通常のポインタを使う場合 ポインタの配列alistと同じように、先にNULLにしておいてからnewして、catch(std::bad_alloc)に入った場合は delete ポインタ。 > 例外が発生しなかった場合デストラクタのほうにも解放処理はちゃんと書く、としていれば、この場合大丈夫でしょうか? 先にNULLになっていません。
その他の回答 (4)
- jacta
- ベストアンサー率26% (845/3158)
> という文章を読んで、持ち出していい話だと思っていたのですが、実際には互換性がない部分が見つかったのでしょうか? それは上位互換性の話ですね。 C++0xの文法を使って書いてしまうと、C++03では当然コンパイルできません。これは移植性が損なわれているということですよね。 しかも、C++0xの仕様はまだ確定していません。正式に規格が制定されたときに、現時点のドラフトに基づいたコードが通用するかどうかはわかりませんよ。 > 実際には互換性がない部分が見つかったのでしょうか? 予約名ではないキーワードを導入している時点でアウトでしょう。 例えば、static_assertはC++03の予約名ではありませんので、そういう名前の識別子をアプリケーションが使っていても文句はいえません。 本当に互換性を100%にするつもりであれば、C1Xのように_Static_assertとすべきですね。
お礼
なるほど、確かにそういう意味ではあれですね(笑) 私はC++ → C++0xというパターンしか頭の中になかったので 普通今C++書いてるという前提なら移植といったら「C++0xに対して」ということになるんじゃないかと思いましたから たとえ何か見つかったとしても、たぶんそれほどは心配いらないだろうと思っていたのですが…w しかしまぁ、予約語についてもそうですが 結局どんなものにも限界はありますし(自分自身が生きていられる時間にも、です)、本当の意味での100%の互換性など、世界中見たらまず起きえないと思うので、私としてはこの質問は「ま、いっか。」ということで締めとさせていただきます。
- jacta
- ベストアンサー率26% (845/3158)
> あと64bitの場合だと0LLと聞きましたが それは処理系によります。 64bitプロセッサでも、アドレス空間が32ビットの場合もありますし、ABIがLP64の場合などは0Lでよいはずですね。 また、代入や比較だけであれば、単に0でも問題ありません。0Lや0LLにしないといけないのは、sizeofのオペランドになったり、既定の実引数拡張が起きるような状況です。 > 移植性と信頼性を最大に高めるなら、 移植性のことをいうのであれば、C++0xを持ち出すべきではありません。 > つまりC++の機能で > deleteを再定義さえしていなければ、この場合はその点では大丈夫ということですね? まだ他にも可能性はあります。 > 適正なアドレスが入っているはずのポインタをdelete した場合、 とのことでしたので、そもそもnewを用いていない場合、具体的には自動記憶域期間や静的記憶域期間を持つ場合や、位置指定形式のnewを用いた場合などは、それらをポインタで受ければ適正なアドレスは入っているわけですが、deleteしようとすると未定義の動作を引き起こします。 未定義なので、(ないとは思いますが)例外が送出されても文句はいえません。
お礼
なるほど んじゃ、64bitは実験できるようになったらその時の目的のOSやコンパイラについて調べてみます。 移植性についてですが http://ja.wikipedia.org/wiki/C%2B%2B0x >ビャーネ・ストロヴストルップ (C++ の開発者であり、標準化委員会のメンバーでもある) の声明によれば、新規格は現行規格とほぼ 100% の互換性を保つとされている という文章を読んで、持ち出していい話だと思っていたのですが、実際には互換性がない部分が見つかったのでしょうか? >deleteについて 私の知りたいことはおそらくわかりました。 ありがとうございます。
- Tacosan
- ベストアンサー率23% (3656/15482)
if (ポインタ) や if (!ポインタ) がどうなるかというのは, 規格を読めば分かります.
お礼
ありがとうございます。 以前なんかの書籍で「規格ではOK」といった内容を読んだような気がしますが、規格書そのものを読んだわけではないです。 というわけで、念のため調べてみたのですが、基本的すぎるのかどこを探せばいいのかさっぱりで、今のところ見つけられていません。どの辺見ればおkでしたでしょうか? そもそもこれはJISX3010サイドの問題ですよね? …んでも、CとC++ではNULLが微妙に違ってたような気がするので… ただし今回の場合もう一つ問題があって「ある規格書の内容が、『それらの環境』」でも通用するのかどうかです。それは特定の言語サイドの規格書を見ても分からない可能性は高いと思います。 (特に少し昔のPCとかだと) 標準規格自体も、不動のものではないはずだからです。 …ん? ってことは、最近の贅沢なOSはだいたいOK、ならばあんまり互換性追求しすぎるより、ある程度絞って処理を高速化するという選択肢もありますね。 Windows以外だとアラインメントとかいう問題まで出てきますから、今のところは後回しに…という方針なのが現状ですし… この件についてはいずれも、マクロをちょこっと使うことで解決できると思いますし。
- jacta
- ベストアンサー率26% (845/3158)
> どういう環境だとならなくなって、その場合その環境ではNULLポインタのアドレスがどういう値になるのか、よろしければご教授ください。 例えば、これはCの場合ですが、 http://www.kouno.jp/home/c_faq/c5.html#17 のような環境もありますし、インタープリタであれば、そういうことがあっても不思議ではないと思います。 > とすればこの場合は規格上も問題ないといえますか? 何をもって問題とするかにもよりますが、普通は問題ないと思います。 > この改良(?)したコンストラクタを正常に抜けた後に > 適正なアドレスが入っているはずのポインタを > delete した場合、単にそれだけの場合に、例外が発生する可能性は、上記コードではないといえるのでしょうか? 可能性がないわけではありません。 例えば、operator deleteを再定義している場合は、あらゆる可能性が考えられます。 もちろん、正しいポインタを渡されて失敗するようなoperator deleteを定義することに問題があるのは間違いありませんが、それはまた別の話です。
お礼
なるほど、ありがとうございます! 違いがあることは分かりました。問題はコード側での対処法ですが、それらの場合でもやはり規格上保証されてるはずなので ポインタ=NULL; と書けば問題ないはず、なのでしょうか? また、そういった場合 if (ポインタ) とか if (!ポインタ) とか言った表記は安全なのでしょうか? あと64bitの場合だと0LLと聞きましたが C++0x では nullptrなら両方対応出来て、かつ整数型との比較や代入はできないことが保証されるということでしょうか? いずれにしても、移植性と信頼性を最大に高めるなら、さらに自分でマクロを作ってそれを使い、後で必要が生じたらその部分だけを変更すればいいようにしておく、のがベストですかね? >例えば、operator deleteを再定義している場合は、あらゆる可能性が考えられます。 もちろん、正しいポインタを渡されて失敗するようなoperator deleteを定義することに問題があるのは間違いありませんが、それはまた別の話です。 つまりC++の機能で deleteを再定義さえしていなければ、この場合はその点では大丈夫ということですね?
お礼
ありがとうございます。 私の環境では全く同じ状態になっているので「なっている」といえますが 「規格上保証されていない」だけでなく >先にNULLになっていません。 ということは、なっていない状況が確実に存在するということですね? どういう環境だとならなくなって、その場合その環境ではNULLポインタのアドレスがどういう値になるのか、よろしければご教授ください。 もし近しい環境でそうなる可能性があると NULLの値が違うということは他の部分にからむ可能性がありますので… 他の点について概ね理解しましたが >デストラクタでは決して失敗しないように設計する必要 というのがやはり具体的に分かりません。 とりあえず、仮にstd::auto_ptr<A> p;をA* p;に変えた場合 コンストラクタ側は B::B(void) try : p(NULL) { for ( int i=10;i; ) alist[--i]=NULL; p=new A; for ( int i=10;i; ) alist[--i]=new A; } catch(std::bad_alloc){ delete p; for ( int i=10;i; ) delete alist[--i]; } とすればこの場合は規格上も問題ないといえますか? おそらくデストラクタが失敗するかどうかは 上記コードだとdelete演算子が失敗するかどうかにかかっていると思うのですが この改良(?)したコンストラクタを正常に抜けた後に 適正なアドレスが入っているはずのポインタを delete した場合、単にそれだけの場合に、例外が発生する可能性は、上記コードではないといえるのでしょうか?