• ベストアンサー

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

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

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

  • ベストアンサー
  • kmb01
  • ベストアンサー率45% (63/138)
回答No.4

#2です。 > たとえば、bのnewでアロケーションを失敗した場合、 > Cのポインタは0で初期化された状態で、deleteしても > 問題ないのでしょうか? > また、Bのnewで失敗した状態のBにもdeleteを実行することになりますが、問題ないのでしょうか? > deleteの順番がポイントなのでしょうか? #3でも書かれていますが、0, NULLのdeleteは問題ありません。 deleteの順番はこの場合はどうでもいいです。 > また、この場合デストラクタA::~A()は実行されるのでしょうか? されません。 例えば A *objA = 0; try { objA = new A; } catch (...) { } で例外が発生した場合、newの時点で例外発生->catchブロックへジャンプするので objAに代入されず、objAは0のままです。 このため、objAのデストラクタを呼ぶことはできません。 したがってコンストラクタで例外が発生する場合は、 例外発生前にコンストラクタ内で確保したりソースを解放してからthrowしなければ そのリソースを解放する手段がなくなります。

kenchan5418
質問者

お礼

ありがとうございました。 シンプルでかつ確実な方法なのでこの方法で実装しようと思います。お手数をおかけしました。

その他の回答 (4)

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

少し出遅れましたが、ひとつずつ回答していきます。 > 質問1)newを失敗した場合には、そのポインタに >     対してdeleteしてはいけないのでしょうか? 原則としては駄目です。 というのも、newに失敗するとstd::bad_alloc例外が創出されるので、そのポインタには何も代入されず、不定状態になります。あらかじめ空ポインタで初期化しておくか、new(std::nothrow)を使って、失敗時に空ポインタが返るようにしておけば問題ありません。 > 質問2)上記3つもエリアの確保のうち、2つめで失敗した場合、1つ目のdeleteをしてやる >     必要はあるのでしょうか 必要です。 根本的な問題を指摘するなら、コンストラクタの中で複数のnewを行うべきではありません。どうしてもの場合は、std::auto_ptrなど、スマートポインタを使うか、内部的にcatchして辻褄を合わせる(#2の方の方法)しかありません。 long型の配列を三つ用意するのであれば、std::vector<long>を三つ使う方が得策です。 # std::auto_ptrを使えないのは、new[]を使うからですよね?

  • sha-girl
  • ベストアンサー率52% (430/816)
回答No.3

#1です。すいません間違ってました。 ISOによるとNULLに対するdeleteは無視されるようです。 よってnewが失敗したポインタをdeleteしても問題ないようです。

  • kmb01
  • ベストアンサー率45% (63/138)
回答No.2

自分ならこう書きます。 class A { private: A(); ~A(); long *a,*b,*c; }; A::A() : a(0), b(0), c(0) { try { a = new long; b = new long; c = new long; } catch (...) { delete c; delete b; delete a; throw; //同じ例外をthrow } } A::~A() { delete c; delete b; delete a; } コンストラクタで例外を投げないようにするには、 クラスAは初期化が完全に成功したかを示すフラグメンバ変数を持ち コンストラクタで例外が起こらなかった場合そのメンバをtrueに設定する。 以降メンバ変数にアクセスする全ての関数の冒頭で 初期化が完全に終わっているかチェックする。 となり、変更箇所が多いためお勧めしません。

kenchan5418
質問者

補足

この方法ですと現在のソースの修正がしやすいので、 もう少し教えていただきたいのですが、 たとえば、bのnewでアロケーションを失敗した場合、 Cのポインタは0で初期化された状態で、deleteしても 問題ないのでしょうか? また、Bのnewで失敗した状態のBにもdeleteを実行することになりますが、問題ないのでしょうか? deleteの順番がポイントなのでしょうか? また、この場合デストラクタA::~A()は実行されるのでしょうか? コンストラクタで例外を投げる方向で設計をしようと思います。よろしくお願いいたします。

  • sha-girl
  • ベストアンサー率52% (430/816)
回答No.1

>質問1)newを失敗した場合には、そのポインタに対してdeleteしてはいけないのでしょうか? 駄目です。newが失敗したポインタにはNULLが入っています。 >質問2)上記3つもエリアの確保のうち、2つめで失敗した場合、1つ目のdeleteをしてやる必要はあるのでしょうか? 1つ目のdeleteをしてやる必要があります。(解放しないと1つ目のメモリが確保されたままになります。) std::auto_ptrを使ってはどうでしょうか? http://www.doumo.jp/postgretips/tips.jsp?tips=78

kenchan5418
質問者

お礼

ありがとうございました。 std::auto_ptrが使えればよかったのですが、 移植性を考えて作る必要があり今回は使用しない 方向で設計をせざるを得なくなりました。 勉強になりました。ありがとうございます。

関連するQ&A

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

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

  • クラスのメンバ変数のnew

    こんにちは。 クラスのメンバ変数の動的割り当てで困っていることがあります。 コンストラクタ内でメンバ変数に動的割り当てをするんですが、そこで例外処理をしなければならないようです。 例 class foo::foo() { try{ hoge = new int[100]; // hogeの代入処理 // } catch { delete[] hoge; hoge = NULL; } } foo::~foo() { delete[] hoge; hoge = NULL; } しかしこれだと不十分なようで、なかしかの対策をしないとメモリリークが起きてしまいそうなんですが どのようにやるんでしょうか?

  • C++ newについて

    お世話になっております。C++初心者です。 newについて質問です。 newして動的にメモリを確保したものはdeleteにて解放処理を 行わないとメモリリークしてしまうのはわかっているのですが、 newした動的メモリに再度newをするとどうなるのでしょうか? また、複数回deleteもせずにnewし続けたあとに deleteをした場合はすべて解放されるのでしょうか? 少し気になったので質問してしまいました。 よろしくお願いいたします。

  • 【C++】new/deleteについて

    deleteについていまいち解らないことがいくつかあります 1 deleteしたポインタを再びnewで確保して使用してもいいのか 2 newしたものは(例えば)関数を抜ける際必ずdeleteするべきなのか それとも抜ける時に自動的に解放されるのか 3 動的オブジェクトの場合も中で動的確保したものを全てdeleteしてからdeleteするべきなのか 何卒よろしくお願いいたします。

  • C++のnewで確保した領域について

    こんにちわ。C++を勉強し始めた者です。 new演算子を使ってインスタンスを生成した場合、それはスタックではなくヒープ領域に確保され、不要になったらdeleteを使って領域を解放しなければいけない認識です。 C++の初心者向けサンプルコードを見ていて疑問があったので質問させてください。 (例)クラスA.cpp ======================== #include <Car> #include <Garage> ~略~ クラスAのコンストラクタ{ Car *mycar = new Car("プリウス"); addGarage( mycar ); } クラスAのデストラクタ{ } ======================== 上記のような実装のクラスAがあったのですが、コンストラクタでCarクラスのインスタンス生成をして、オート変数の*mycarに格納して、Garageの公開関数に渡しています。 質問1:このクラスAをインスタンス生成した場合、コンストラクタで確保したヒープ領域は、プログラム終了時まで解放されない認識であっていますか? 質問2:オート変数の*mycarはコンストラクタからreturnした時点で解放されてしまうので、今のままではデストラクタでヒープ領域をdeleteできない認識であっていますか? 質問3:newで生成したインスタンスへのポインタは、その関数内でdeleteしない場合、メンバ変数やstatic変数、グローバル変数に格納しなければdeleteできなくなるという理解であっていますか? 質問4:C++のコードでnewした戻り値をオート変数に格納するプログラムは通常使うことはあるのですか?

  • VC++のnewでNULLが返る

    現在VC++で3Dゲームのプログラムを作成中なのですが、プログラムの一番最初の部分で、ある関数を呼び出し内部で構造体を宣言、 その後にnewでメモリ動的確保を行うとNULLが返るようになってしまいました。 関数のソースはこんな感じです↓。 LPOBJ LoadObj( LPSTR filename ) {      LPOBJ lpObj;// LPOBJはOBJ構造体のポインタです      // 構造体メモリ確保      lpObj = new OBJ;//  この時点で失敗してNULLが返る      //      // いろいろと処理      //      return lpObj; } ↑この関数の戻り値をクラスのコンストラクタでメンバ変数にセットしています。 先日プログラムを動かしている時に急にこの状態(newでNULLが返る)になり、それ以来ずっとこの調子です。 (現在は)メモリーリークは起こしていません。(少し前までは大量に起こしていました) この状態になるともうメモリの動的確保はできないのでしょうか?

  • デストラクタを呼びたい

    デストラクタを明示的に呼び出したいです。 VC++.net 2003を使っています。 CTest* pTest = new(アドレス) CTest(); という形で独自に確保した領域にインスタンスを作成 していくのですが、 普通にnewしたのならdelete pTest;で良いと思います。 しかしmalloc等で確保した先頭ポインタが入っているわけではないので delete pTest;とすると例外が発生します。 そこでpTestのデストラクタを明示的に呼び出したいのですが 可能でしょうか?

  • newについて

    newを使った場合、3バイト分確保したつもりでしたが、7バイト?になります。どうしてでしょうか? newした場合はdeleteをしないといけませんが、deleteした後にNULLで初期化する意味はあるのでしょうか? CとC++が混在?(newはC++かな?)しているソースなのですが、 現場(クリティカルな開発)ではこういう書き方はNGでしょうか? 組み込み系のお仕事をされている方の意見も聞きたいです。 #include <string.h> void main(void){ char *cstr = new char [3]; printf("%d\n", strlen(cstr)); // 3を期待していたのですが、7になります。 if (cstr != NULL){ delete cstr; cstr = NULL; } }

  • C++のnewの使い道

    最近C++を勉強しているのですが、new演算子はどういうときに使うべきでしょうか? メモリを動的確保できるのはわかります。 Cのmallocのようなものと考えていいのでしょうか? つまり、関数内でメモリを確保してそのアドレスを返すとか、コンパイル時に不明なサイズのメモリを実行時に確保するとか、任意のタイミングでメモリを開放したい場合などに使うものでしょうか。 ある参考書を読んでいると「引数つきコンストラクタを呼び出す場合はnewでインスタンス生成する」と書いてありました。 デフォルトコントラスタならnewなし、引数付きならnewあり、ということらしいです。 なぜこんなことをする必要があるのでしょうか?

  • 基本クラスポインタ = new 派生クラス[i];

    基本クラスのポインタ変数pbaseを宣言し、new演算子にて派生クラスの配列を動的に確保して、pbaseに代入した場合、delete[] pbaseは上手く動作するのでしょうか。 (Aのデストラクタは仮想関数にしてあるとしておきます) ------ex-start------ class A {}; class b:public A{}; main() {   A * pbase   pbase = new B[5];   delete[] pbase } ------ex-end------ 例えば、 class Aは12バイト class Bは20バイト である場合、配列のサイズが違うのに、delete[]でちゃんと開放されるのでしょうか。 それともnew/deleteは確保したサイズをシンボル毎に記憶しているのでしょうか。 どうもこの辺が曖昧で実装する時にあやふやになってしまいます。どなたかお知恵をお貸しくださいませ。