• ベストアンサー

コンストラクタやデストラクタと例外について

対処法が「概念的に」書かれているサイトや書籍は結構あるようなのですが、具体的なコードでビシッと書かれてることや、そもそも根本的に「どういう場合に例外が発生するのか」の、帰納的な説明はなかなか見つからないので、もやもやしています。 また、「人が書いたコードを使う場合に全部読んでられないような状況」という前提が暗黙にあって書かれている文章が多いと感じます。 しかし、根本的なところが分からないともやもや感は拭えません。 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 ポインタ。 例外が発生しなかった場合デストラクタのほうにも解放処理はちゃんと書く、としていれば、この場合大丈夫でしょうか?

質問者が選んだベストアンサー

  • ベストアンサー
  • jacta
  • ベストアンサー率26% (845/3158)
回答No.1

> 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になっていません。

LongSecret
質問者

お礼

ありがとうございます。 私の環境では全く同じ状態になっているので「なっている」といえますが 「規格上保証されていない」だけでなく >先に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 した場合、単にそれだけの場合に、例外が発生する可能性は、上記コードではないといえるのでしょうか?

その他の回答 (4)

  • jacta
  • ベストアンサー率26% (845/3158)
回答No.5

> という文章を読んで、持ち出していい話だと思っていたのですが、実際には互換性がない部分が見つかったのでしょうか? それは上位互換性の話ですね。 C++0xの文法を使って書いてしまうと、C++03では当然コンパイルできません。これは移植性が損なわれているということですよね。 しかも、C++0xの仕様はまだ確定していません。正式に規格が制定されたときに、現時点のドラフトに基づいたコードが通用するかどうかはわかりませんよ。 > 実際には互換性がない部分が見つかったのでしょうか? 予約名ではないキーワードを導入している時点でアウトでしょう。 例えば、static_assertはC++03の予約名ではありませんので、そういう名前の識別子をアプリケーションが使っていても文句はいえません。 本当に互換性を100%にするつもりであれば、C1Xのように_Static_assertとすべきですね。

LongSecret
質問者

お礼

なるほど、確かにそういう意味ではあれですね(笑) 私はC++ → C++0xというパターンしか頭の中になかったので 普通今C++書いてるという前提なら移植といったら「C++0xに対して」ということになるんじゃないかと思いましたから たとえ何か見つかったとしても、たぶんそれほどは心配いらないだろうと思っていたのですが…w しかしまぁ、予約語についてもそうですが 結局どんなものにも限界はありますし(自分自身が生きていられる時間にも、です)、本当の意味での100%の互換性など、世界中見たらまず起きえないと思うので、私としてはこの質問は「ま、いっか。」ということで締めとさせていただきます。

  • jacta
  • ベストアンサー率26% (845/3158)
回答No.4

> あと64bitの場合だと0LLと聞きましたが それは処理系によります。 64bitプロセッサでも、アドレス空間が32ビットの場合もありますし、ABIがLP64の場合などは0Lでよいはずですね。 また、代入や比較だけであれば、単に0でも問題ありません。0Lや0LLにしないといけないのは、sizeofのオペランドになったり、既定の実引数拡張が起きるような状況です。 > 移植性と信頼性を最大に高めるなら、 移植性のことをいうのであれば、C++0xを持ち出すべきではありません。 > つまりC++の機能で > deleteを再定義さえしていなければ、この場合はその点では大丈夫ということですね? まだ他にも可能性はあります。 > 適正なアドレスが入っているはずのポインタをdelete した場合、 とのことでしたので、そもそもnewを用いていない場合、具体的には自動記憶域期間や静的記憶域期間を持つ場合や、位置指定形式のnewを用いた場合などは、それらをポインタで受ければ適正なアドレスは入っているわけですが、deleteしようとすると未定義の動作を引き起こします。 未定義なので、(ないとは思いますが)例外が送出されても文句はいえません。

LongSecret
質問者

お礼

なるほど んじゃ、64bitは実験できるようになったらその時の目的のOSやコンパイラについて調べてみます。 移植性についてですが http://ja.wikipedia.org/wiki/C%2B%2B0x >ビャーネ・ストロヴストルップ (C++ の開発者であり、標準化委員会のメンバーでもある) の声明によれば、新規格は現行規格とほぼ 100% の互換性を保つとされている という文章を読んで、持ち出していい話だと思っていたのですが、実際には互換性がない部分が見つかったのでしょうか? >deleteについて 私の知りたいことはおそらくわかりました。 ありがとうございます。

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.3

if (ポインタ) や if (!ポインタ) がどうなるかというのは, 規格を読めば分かります.

LongSecret
質問者

お礼

ありがとうございます。 以前なんかの書籍で「規格ではOK」といった内容を読んだような気がしますが、規格書そのものを読んだわけではないです。 というわけで、念のため調べてみたのですが、基本的すぎるのかどこを探せばいいのかさっぱりで、今のところ見つけられていません。どの辺見ればおkでしたでしょうか? そもそもこれはJISX3010サイドの問題ですよね? …んでも、CとC++ではNULLが微妙に違ってたような気がするので… ただし今回の場合もう一つ問題があって「ある規格書の内容が、『それらの環境』」でも通用するのかどうかです。それは特定の言語サイドの規格書を見ても分からない可能性は高いと思います。 (特に少し昔のPCとかだと) 標準規格自体も、不動のものではないはずだからです。 …ん? ってことは、最近の贅沢なOSはだいたいOK、ならばあんまり互換性追求しすぎるより、ある程度絞って処理を高速化するという選択肢もありますね。 Windows以外だとアラインメントとかいう問題まで出てきますから、今のところは後回しに…という方針なのが現状ですし… この件についてはいずれも、マクロをちょこっと使うことで解決できると思いますし。

  • jacta
  • ベストアンサー率26% (845/3158)
回答No.2

> どういう環境だとならなくなって、その場合その環境ではNULLポインタのアドレスがどういう値になるのか、よろしければご教授ください。 例えば、これはCの場合ですが、 http://www.kouno.jp/home/c_faq/c5.html#17 のような環境もありますし、インタープリタであれば、そういうことがあっても不思議ではないと思います。 > とすればこの場合は規格上も問題ないといえますか? 何をもって問題とするかにもよりますが、普通は問題ないと思います。 > この改良(?)したコンストラクタを正常に抜けた後に > 適正なアドレスが入っているはずのポインタを > delete した場合、単にそれだけの場合に、例外が発生する可能性は、上記コードではないといえるのでしょうか? 可能性がないわけではありません。 例えば、operator deleteを再定義している場合は、あらゆる可能性が考えられます。 もちろん、正しいポインタを渡されて失敗するようなoperator deleteを定義することに問題があるのは間違いありませんが、それはまた別の話です。

LongSecret
質問者

お礼

なるほど、ありがとうございます! 違いがあることは分かりました。問題はコード側での対処法ですが、それらの場合でもやはり規格上保証されてるはずなので ポインタ=NULL; と書けば問題ないはず、なのでしょうか? また、そういった場合 if (ポインタ) とか if (!ポインタ) とか言った表記は安全なのでしょうか? あと64bitの場合だと0LLと聞きましたが C++0x では nullptrなら両方対応出来て、かつ整数型との比較や代入はできないことが保証されるということでしょうか? いずれにしても、移植性と信頼性を最大に高めるなら、さらに自分でマクロを作ってそれを使い、後で必要が生じたらその部分だけを変更すればいいようにしておく、のがベストですかね? >例えば、operator deleteを再定義している場合は、あらゆる可能性が考えられます。 もちろん、正しいポインタを渡されて失敗するようなoperator deleteを定義することに問題があるのは間違いありませんが、それはまた別の話です。 つまりC++の機能で deleteを再定義さえしていなければ、この場合はその点では大丈夫ということですね?

関連するQ&A

  • コンストラクタ・デストラクタ

    プログラミング言語はC++ C++を触り始めたばかりの素人です。 コンストラクタとデストラクタについて質問です。 下記に参考にしているウェブページから簡潔にしてコードを書いてみました。 コンストラクタ、デストラクタの中はそれぞれに、○○が呼び出されましたと書いてあるだけでよく分からなかったので別のウェブページを見たら コンストラクタは Sample::Sample(){ n=0; } みたいな例があったのですが、このように変数に予め何かの値を代入しておくという事で合ってますか? デストラクタは理解できていません。 下記の例では、どのような処理を書けばいいのでしょうか? #include<iostream> using namespace std; class Sample { private: int n; public: void Show(); Sample(); ~Sample(); }; void Sample::Show() { cout << n << endl; } Sample::Sample() { // n=0; std::cout << "コンストラクタが呼び出されました" << std::endl; } Sample::~Sample() { // どんな処理? std::cout << "デストラクタが呼び出されました" << endl; } main() { Sample sample; sample.Show(); return 0; } 実行結果 コンストラクタが呼び出されました 1     ←コンストラクタ関数内の//を削除で0になることは確認 デストラクタが呼び出されました

  • 標準の例外の全種類と、発生する状況、対処法を把握したいのですが

    自ら例外を全くthrowしないコードにおいて、発生する可能性がある例外は こちらの http://www.geocities.jp/ky_webid/cpp/library/027.html std::bad_alloc std::bad_cast std::bad_typeid std::bad_exception std::ios_base::failure [std::logic_error] ├ std::invalid_argument ├ std::length_error ├ std::out_of_range └ std::domain_error [std::runtime_error] ├ std::range_error ├ std::overflow_error └ std::underflow_error で全てですか? std::bad_allocは目を瞑るとして(コードを書くことで対処するとして) その他の全ての発生可能性を消せれば、try catch を完全に意図通りに制御できると考えて良いでしょうか? なお、現在 std::bad_allocは良いとして これらのうち std::bad_typeid std::ios_base::failure std::invalid_argument std::range_error std::overflow_error std::underflow_error については、自ら明示的に投げずに実際に発生する状況を確認できていません。 発生させられるコードをご存じでしたらご教授ください。 その他については以下のコードで発生したり、または状況確認がとれました。 また、この解釈で正しいでしょうか? /////////   std::bad_cast   ////////// #include <typeinfo> class Base { public: virtual ~Base(){} }; class Sub : public Base{ }; Base b; Sub& s = dynamic_cast<Sub&>( b ); //Sub* s = dynamic_cast<Sub*>( &b ); ならsがNULLになるだけで例外は飛ばない。 対処法: 継承関係を完全に把握出来れば発生確率0にできるはず。 そもそもポリモーフィズムにしない状況で、しかも参照としてダウンキャストするというケースはあまりないほうがいいのではないかな? /////////   std::bad_exception   ////////// 警告C4290(http://msdn.microsoft.com/ja-jp/library/sa28fef8%28VS.80%29.aspx) が出るので発生自体は確認できませんでした。 対処法: 仮に対応したとしても書かなければこれ自体は起こり得ないのでは…? /////////   std::out_of_range   ////////// #include <stdexcept> #include <vector> std::vector<int> i(1); i.at(1); 対処法: 普通に配列使うときと同じように プログラマが注意払ってれば大丈夫のはず。 /////////   std::length_error   ////////// #include <vector> std::vector<int> i(-1); 対処法: やはり注意深ければ起こらないのではないかと。 /////////   std::domain_error   ////////// 対処法: リンク先の解説より、書かなければ発生しないはず。(?)

  • コンストラクタでnewを失敗した場合の対応について

    よろしくお願いします。 クラスのメンバで3つのlongのポインタを宣言し、 コンストラクタ内でそれぞれにnewして領域を確保 しています。 質問1)newを失敗した場合には、そのポインタに     対してdeleteしてはいけないのでしょうか? 質問2)上記3つもエリアの確保のうち、2つめで失    敗した場合、1つ目のdeleteをしてやる     必要はあるのでしょうか? catch(bad_alloc)でその処理をしようとおもうのですが、そもそもコンストラクタで例外を発生させるなと かかれている書籍もあるようです。ただ、すでにそういう記載になってしまっており、できれば、いまの構造でメモリーリークを防げないかと思案しております。どなたか、よい方法をご存知の方いらっしゃいましたら、アドバイスいただけましたら幸いでございます。

  • デストラクタについて

    #include <iostream> #include <string> using namespace std; #define NUM 2 //登録人数 class Person{ char *name; int *age; char *hobby; public: Person() { name = new char; age = new int; hobby= new char; } void Set(char *n,int a,char *h) { name=n; *age=a; hobby=h; } char *Get_name(void) { return name; } int Get_age(void) { return *age; } char *Get_hobby(void) { return hobby; } ~Person() { cout<<name<<"のデストラクタ\n"; delete [] name; delete age; delete [] hobby; } }; int main(void) { Person *p; int i; p=new Person[NUM]; p[0].Set("永嶋",21,"映画鑑賞"); p[1].Set("平林",54,"車"); for(i=0;i<NUM;i++){ cout<<"\n名前:"<<p[i].Get_name(); cout<<"\n年齢:"<<p[i].Get_age(); cout<<"\n趣味:"<<p[i].Get_hobby()<<"\n"; } delete [] p; return 0; } というプログラムを作成したのですが デストラクタの3つのdeleteがおかしいようなのですが どのような部分が問題となっているのでしょうか? 回答・アドバイス宜しくお願いいたします。

  • C#デストラクタが走る理由がわからない(初心者)

    WPF C# アプリを初めて組んでいます。 MainWindowクラスで、publicなクラス変数配列(myclass)を定義し、 MainWindowのコンストラクタ(もしくはLoadedイベント)で、上記クラス変数にインスタンスを生成しています。 そのインスタンスを、MainWindows内のコントロールイベント(Button_Click)で参照しようとするとNullとなっておりました。 MainWindowのコンストラクタ(もしくはLoadedイベント)完了後に、デストラクトが走っているようです。 どこからも参照されていなければGCが走りデストラクタされるのはわかるのですが、 この場合、MainWindowクラスのメンバにクラス変数を追加し、そこにインスタンスを生成しています。よって、MainWindow(つまりアプリが)が終了するまでインスタンスが参照されているため、 デストラクタが走らないのではないかと考えておりました。 ですが、上記の通り、MainWindowのコンストラクタ(もしくはLoadedイベント)後には、MyClassデストラクタが走ってしまいます。この辺を教えていただけないでしょうか。 また、下記のように、Clickでインスタンスを参照するにはどうすればよいでしょうか。 public partial class MainWindow : Window {     MYClass []myclass;     public MainWindow()     {         InitializeComponent();         //ファイル読み込み(記載省略)         for(i=0; i<ファイル読み込み数;i++)         {             myclass[i] = new myclass(ファイル読み込みデータ);         }     }     private void Button_Click(object sender, RoutedEventArgs e)     {         for(int i=0;i<myclass.Length;i++) ←ここに来る前にすでにMyClassのデストラクタが走っており、nullとなっている         {             data = myclass[i].data;         }     } }

  • デフォルトコンストラクタについて

    クラスについて、デフォルトコンストラクタについて理解が乏しいのですが、クラスB側で何か細工をすると、クラスAのデフォルトコンストラクタ無しで、エラーなくコンパイルできるのでしょうか? 派生クラスの親のベースクラスのコンストラクタを呼ぶsuper?でOKなのでしょうか? #include "stdafx.h" class A { public: //A(){} // これをコメントアウトにすると // error C2512: 'B' : クラス、構造体、共用体にデフォルト コンストラクタがありません。 A(int x){} ~A(){}; }; class B : A { }; void main(void){ B b; }

  • C++のクラスについて

    /*以下のコメントがある行では何故、コンストラクタ(class2::class2)を指定出来ないのですか? デストラクタ(class2::~class2)の場合も問題なくコンパイルが通り、実行できます (http://codepad.org/1oJkxjyZ の23行目) 開発環境 Windows XP SP3 コンパイラ:GCC 実行結果 class1のコンストラクタ class2のコンストラクタ aiueoの実行 class2のデストラクタ class1のデストラクタ */ #include<iostream> class class1; class class2; class class1{ public: class1(); ~class1(); private: class2*pointer; }; class class2{ public: class2(); ~class2(); void aiueo(); }; class1::class1(){ std::cout<<"class1のコンストラクタ"<<std::endl; pointer=new class2(); pointer->aiueo(); //aiueoを~class2に置き換えてもコンパイル出来るが、class2だとエラーが出る } class1::~class1(){ delete pointer; std::cout<<"class1のデストラクタ"<<std::endl; } class2::class2(){ std::cout<<"class2のコンストラクタ"<<std::endl; } class2::~class2(){ std::cout<<"class2のデストラクタ"<<std::endl; } void class2::aiueo(){ std::cout<<"aiueoの実行"<<std::endl; } int main(){ class1 test1; return 0; }

  • 試作クラス使用C++プログラムが動かない原因

    試しに作成した以下のプログラムにビルドエラーが発生し,困っているので質問しました. コンパイラは,「BBB *b」などクラスポインタ変数の部分がエラーと指摘しているのですが(他にも数か所ありますが…),間違っている理由が分からず困ってます. ご回答,よろしくお願い致します. ----------------------------------- #include <iostream> using namespace std; class AAA { public: // コンストラクタ AAA(){ b = new BBB( this ); } // デストラクタ ~AAA(){ delete b; } // メンバ変数 BBB *b; int i; // メンバ関数 void displayB(){ b->displayA(); } }; class BBB { public: // コンストラクタ BBB( AAA *a ){ this->a = a; } // デストラクタ ~BBB(){} // メンバ変数 AAA *a; int j; // メンバ関数 void displayA(){ printf( "%d\n", a->i ); } }; int main() { // 変数の定義 AAA a; // 変数の初期化 a.i = 2; a.b->j = 5; // 出力 printf( "%d\n", a.i ); a.b->displayA(); return 0; } -----------------------------------

  • 例外処理についてご相談

    こんばんは。 とあるユーザー定義例外クラスがあるとします。 でこれのコンストラクタが sendException (String errId) sendException (String errId,String errMsg) sendException (String errId,String errMsg,Throwable e) と3種類あるとします。 でCatchする方の記述で困っています。 実は上記例外の送出は既存システムで、3種類全て使用されているのです。 つまり最初の xxxException (String errId) が送出されてきて、Catch側で「Throwable e」を取り出そうとしたら 「NullPointerException」が発生するような気がします。 Catch側でどのコンストラクタでExceptionが生成されたか、チェック する方法はありますでしょうか?。 CatchしたExceptionのプロパティのNull確認をするしかありませんでしょうか・・。 アドバイスお願いします。

  • コンストラクタ

    恐れ入ります。 たとえばSample classを定義するとして class Sample{ int a=0; int b=0; Sample(){ //まあ、この場合は通常省略ですが。 } } とコンストラクタの前に処理を書く場合と、 class Sample{    Sample(){ int a=0; int b=0; } とコンストラクタに書く場合とでは何か違いがあるのでしょうか?本を見ると「コンストラクタはインスタンスが生成されたときに呼び出される処理」とありますが、上のような記述でも「生成されたときに処理」されますよね・・・? インスタンスを生成するときに引数を使う場合には違いはわかるのですが・・・? 引数がない場合には「コンストラクタには意味はない」であっていますか?

    • ベストアンサー
    • Java

専門家に質問してみよう