• ベストアンサー

shared_ptrからpimplのデストラクタ呼び出し

pimplイディオムの勉強をするために以下のようなプログラムを作りました。 //MySharedPtr.h template<typename tnT> class MySharedPtr {  tnT *mPtr; public:  MySharedPtr(tnT *ptr) : mPtr(ptr) {}  ~MySharedPtr() { delete mPtr; } //(1) }; //Foo.h class Foo {  struct stImpl;  MySharedPtr<stImpl> mImpl; //(2) public:  Foo(); }; //Foo.cpp Foo::stImpl {  ~stImpl() { cout << "xxx" << endl; } //(3) }; Foo::Foo() : mImpl(new stImpl) {} //main.cpp int main() {  Foo foo; } これをコンパイルすると、(1)の所でtnTのデストラクタが見つからない旨のワーニングが出て、mainを実行するとstImplのデストラクタが呼ばれずに(3)の出力は出ません。ただ、(2)をboost::shard_ptrに変えるとワーニングも出ないし(3)の出力もされます。(2)の箇所でstImplが宣言だけなのは両者とも同じだと思うのですが、なぜboostはワーニングが出ないのでしょうか。また、MySharedPtrでもワーニングを出さないようにすることはできるのでしょうか。もちろん、スマートポインタを実装するよりもboostを使用した方が良いとは思うのですが、何でboostはうまくいくのか不思議に思い、質問させていただきました。

  • kary
  • お礼率95% (82/86)

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

  • ベストアンサー
  • 1839cc
  • ベストアンサー率54% (12/22)
回答No.7

> クラスのデストラクタの先頭にはデータメンバのデストラクタを呼び出すコードがコンパイラにより自動的に追加されるとあります > (これは概念を表現しただけのものかもしれませんが、理解する上では問題ないと思います)。 メカニズムの話ですね。 いえ、概念ではなく、実際に追加されると考えて間違いないと思います。 ただ、ここで言う「コード」というのは「ソースコード」ではなく「実行コード」の意味と考えてください。 > つまり、Fooクラスの宣言位置でFooのデストラクタをinline化した場合はstImplが宣言だけの不完全体であるために問題がありますが、 > stImplの定義が終わった後に明示すればstImplの詳細が見えるので問題がなくなるとの事でしょうか。 そのとおりです。 ただ、このメカニズムのみを考えているとNo2の回答に至るわけです。 このとき私はpimplを知りませんでした。 pimplの設計思想を踏まえたら、No6の回答に至ったわけです。 でも、そもそも、なんで不完全体の構造体をdeleteできちゃうんでしょうねぇ。 コンパイルエラーが出てくれたほうが親切だと思うんですが。 > 一般的にコンストラクタはstImplの定義よりも後に書かれるはすなので そのとおりです。 ただ、Foo::stImplをnewするコードと、mImplにそのアドレスを設定するコードが異なるコンパイル単位である場合、安全ではなくなってしまいます。 ただ、そのような構造はpimplの設計思想と反しているので、考慮する必要はないのでしょうね。

kary
質問者

お礼

色々お教えいただきましてありがとうございました。boostで警告がでない理由と、自作のスマートポインタで警告を出さない方法を知ることができました。とくにboostで用いられています「動的削除子」は今まで知らなかった考え方でしたのでとても勉強になりました。

その他の回答 (6)

  • 1839cc
  • ベストアンサー率54% (12/22)
回答No.6

> ただひとつ、何でstImplのデストラクタの話をしているのにFooのデストラクタが重要になるのかとの素朴な疑問があります。 > たぶん非常に基本的な話なように思いますので今後の勉強課題にしたいと思います。 ・・・答えちゃっても良いのかな? それはメカニズムがいまいち納得できないのでしょうか? それとも、論理的に納得できないのでしょうか? とりあえず、論理的な説明を以下にご回答いたします。 pimplイディオムの目的は、 stImplの実装をFoo以外から隠蔽することだったはずですよね? 逆に言えば、stImplの実装を知っているのはFooだけということになりますから、 FooはstImplの取扱いに関して全責任を負うべきです。 要するに、FooはstImplのデストラクタコールを保障する義務も持っているわけですね。 これが、Fooの実装方法が重要となる理由です。 (具体的な方法は、「stImplのデストラクタコールを全て 『stImplの詳細が見える位置』に記述する」だけです) ということは・・・ そもそもstImplのデストラクタコールをSharedPtrに委譲してしまう時点で Fooの設計は間違っていると言えそうです。 SharedPtrはpimplイディオムのデストラクタコールを保障していないのですから。 stImplの削除ファンクタをSharedPtrに渡すことができれば安心なのですが、 SharedPtrはそのようなインターフェイスも持っていません。 もっとも、boost::shared_ptrのように、 pimplイディオムのデストラクタコールも保障できるSharedPtrを 使用するという方法は良いアイディアだとおもいますよ。 そのように、MySharedPtrを拡張してみてはいかがですか? NO4,5を参考にすれば実現できると思います。 一方、FooのデストラクタをFoo.cpp内に実装するという、 私たちが提案した解決方法なのですが、 委譲先であるMySharedPtrの実装に依存しているため、 正直あまり誉められたものとは思いません。 (勉強になりました!自信ありとしていたのがバカみたいです・・・笑) このような方法よりは、SharedPtrを使用しない方がまだマシですね。

kary
質問者

お礼

度々のご回答、ありがとうございます。私なりにいろいろ調べた結果、以下のような事なのかなぁと考えます。「Effective C++ 3rd」の30項によりますと、クラスのデストラクタの先頭にはデータメンバのデストラクタを呼び出すコードがコンパイラにより自動的に追加されるとあります(これは概念を表現しただけのものかもしれませんが、理解する上では問題ないと思います)。今回の例の場合は、明示であれ暗黙であれFooのデストラクタは、 Foo::~Foo() {  mImpl.stImpl::~stImpl(); } のようになるのだと思います。mImplのデストラクタではstImplをdeleteするために、それを静的に解決しようとするとFooのデストラクタの位置が重要になるのだと思います。つまり、Fooクラスの宣言位置でFooのデストラクタをinline化した場合はstImplが宣言だけの不完全体であるために問題がありますが、stImplの定義が終わった後に明示すればstImplの詳細が見えるので問題がなくなるとの事でしょうか。また、以上の問題はmImplのdeleteを静的に解説する場合の問題であり、動的に解決する場合にはどこにFooのデストラクタがあっても問題がなくなるとの事だと思いました(Fooのコンストラクタで動的にdeleterを作成すればよい。一般的にコンストラクタはstImplの定義よりも後に書かれるはすなので)。当初とは異なる部分まで質問が及んでしまいまいたが、ご丁寧に回答頂きありがとうございました。

  • 1839cc
  • ベストアンサー率54% (12/22)
回答No.5

何度もすみません。 コードに間違いがいくつか。 MyScopedPtr のデストラクタが ~MySharedPtr になってますが、間違いです。 MyScopedPtr::destructor の型は AbstractDestructor* 型の間違いです。 となると、Destructor クラスの設計も変更ですねぇ。 struct AbstractDestructor {   virtual void operator() (void*) = 0; }; template <typename T> struct Destructor : public AbstractDestructor {   virtual void operator() (void* p)     { delete static_cast<T*>(p); } }; ちなみに、実は参考URLもほとんど読まずに書いてますので、boostとはかなり異なるかもしれません。 ただ、目的のことは実現できるはずです。

kary
質問者

お礼

参考URL、ありがとうございます。静的にデストラクタを解決しようとすると、FooのデストラクタからstImplの定義が見える必要があり、ただ、暗黙のデストラクタはinlineなので明示しないとバグになる。一方、deleteを動的に解決すればFooのデストラクタから定義が見えなくても問題ない(deleterを生成する箇所(コンストラクタ)から定義が見えればよい)とのことなのですね。非常に参考になりました。ただひとつ、何でstImplのデストラクタの話をしているのにFooのデストラクタが重要になるのかとの素朴な疑問があります。たぶん非常に基本的な話なように思いますので今後の勉強課題にしたいと思います。ありがとうございました。

  • 1839cc
  • ベストアンサー率54% (12/22)
回答No.4

> おそらく、Foo のコンストラクタの定義を main.cpp に移せば表示されなくなると思いますよ。 これはウソでした! ウソって言うより、そもそもコンパイルが通るわけがないです。 stImplの確保自にサイズが分からないわけですからね。 当然コンパイルエラーです。 > ただの偶然です。 ごめんなさ~い! コレもウソでした!! boostはちゃんと意識して設計されているようですね。 やはり、コンストラクタ時に、仮想関数を利用してデストラクタを決定しているようですね。 大体、以下のような機構ではないでしょうか。 詳細は参考URLをご覧ください。 ////////////////////// MyScopedPtr.h ///////////////////////////// class AbstractDestructor { protected:   virtual void Destruct(void*) = 0; }; template <typename T> class Destructor : public AbstractDestructor { public:   void operator(T* p) { Destruct(p); }    protected:   virtual void Destruct(void* p) { delete p; } }; tempalte <typename T> class MyScopedPtr {   T*        p;   Destructor<T>  *destructor;    public:   MyScopedPtr(T* p_) : p(p_), destructor(new Destructor<T>()) {}   ~MySharedPtr() { (*destructor)(p); }   void reset(T* p_)   {     (*destructor)(p);     delete destructor;     p = p_;     destructor = new Destructor<T>();   } }; ///////////////////////// main.cpp /////////////////////////////// int main() {   MyScopedPtr<Hoge> ptr(new Hoge);     // Destructor::Destruct は、MyScopedPtr::MyScopedPtrもしくはMyScopedPtr::resetで特殊化される。     // 二つの関数は普通 new と同時に呼ばれるため、Hogeの構造を知っているコンパイル単位に記述される。     // したがって、Destructor::Destruct は、~Hoge を知っていることが期待できる。   return 0; }

参考URL:
http://d.hatena.ne.jp/Cryolite/20060108
  • 1839cc
  • ベストアンサー率54% (12/22)
回答No.3

ちなみに、なぜ Boost では正しく呼ばれるのか、ですが、 あくまで予想ですが、おそらくデストラクタが呼ばれるまでの過程に、virtual関数コールが含まれているのでしょう。 virtual関数は、コンストラクタによって初期化されます。 質問者さんのコードでは、Foo のコンストラクタは Foo.cpp 側に書かれていました。 つまり、~stImpl() が見える位置で mImpl の virtual 関数が初期化されているのでしょう。 おそらく、Foo のコンストラクタの定義を main.cpp に移せば表示されなくなると思いますよ。 NO1さんの解説を見るとコンパイルが通らなくなるのかも・・・

kary
質問者

お礼

今回の例につきましてclとgccでいろいろ試してみたのですが、boostはいずれでも問題なく動きました(デストラクタを明示する・しないにかかわらず)。boostで問題がない理由は良くわからないというのが正直な所でした。ご回答ありがとうございました。

  • 1839cc
  • ベストアンサー率54% (12/22)
回答No.2

> 何でboostはうまくいくのか不思議に思い、質問させていただきました。 ただの偶然です。 バグはまったく別の場所にあります。 まず、そのバグの説明からしていきましょう。 Foo.hがインクルードされた際、二つのソースコードは以下のようになっています(大体のイメージです)。 ちなみに以下のコード中には、「暗黙のメンバ関数」とよばれる、コンパイラによって自動的に生成される関数を、いくつか追加してあります。 //////////////// Foo.cpp //////////////////// #include "MySharedPtr.h" class Foo {   struct stImpl;   MySharedPtr<stImpl> mImpl; public:   Foo();   ~Foo() {} }; struct Foo::stImpl {   stImpl() {}   ~stImpl() { cout << "xxx" << endl; }  // (1) }; Foo::Foo() : mImpl(new stImpl) {} //////////////// main.cpp //////////////////// #include "MySharedPtr.h" class Foo {   struct stImpl{     stImpl() {}     ~stImpl() {}  // (2)   };   MySharedPtr<stImpl> mImpl; public:   Foo();   ~Foo() {} }; int main() {   Foo foo; } お気づきになられましたでしょうか。 なんと ~stImpl() が二種類作られてしまっているのです。 なぜ、このようなことになるのでしょう。 質問者さんのコードでは、struct stImpl の定義が Foo.cpp に書かれています。 しかし、main.cpp は、Foo.h をインクルードしているだけですので、Foo.cpp にある定義を知ることは出来ません。 つまり、main.cpp からみると、定義のない構造体なのです。 この場合、stImpl にはデストラクタがありませんから、main.cpp のコンパイル時に、デストラクタなどのメンバ関数が暗黙的に生成されます。 それが、私の示したコードになります(実際は4種類の暗黙関数が生成されています)。 main関数から直接見えるデストラクタを辿ってみてください。 (2)のデストラクタにたどり着くはずです。 おそらく、Foo のデストラクタの定義を Foo.cpp に記述すれば、(1) 側のデストラクタが呼ばれるようになると思います(inline 関数として、Foo.h に定義すれば、(2)が呼ばれるのでは?)。 ただし、暗黙の関数がinline展開されるのは、コンパイラ固有の動作だと思われますので、すべてのコンパイラが同じ動作をするわけではありません。

kary
質問者

お礼

ご回答ありがとうございます。デストラクタを明示したら解決いたしました。また、ご指摘頂きましたようにデストラクタをinlineにした場合にはstImplのデストラクタが呼ばれませんでした。

  • rabbit_cat
  • ベストアンサー率40% (829/2062)
回答No.1

これは、Pimplの有名な問題でして、 本来の、正しい解決法は、 Foo.hに、Fooクラスのデストラクタの宣言 ~Foo(); を明示的に書いて、 Foo.cppの一番最後(Foo:stImplの定義が終わったあとに) Foo:~Foo() {} とFooの(空の)デストラクタを定義することです。 boost::scoped_ptr や boost::shared_ptrでは、 boost::checked_deleteてやつが使われていて、不完全なクラスのdeleteが起きないようにチェックしてくれます。 ------ boost::shared_ptrでうまくいってしまう理由はよくわかりません。 Pimpleのポインタを持つだけなら、boost::scoped_ptrで十分でして、boost::shared_ptrはオーバースペックなわけですが、 Fooの明示的なデストラクタ宣言がない状態で、boost::shared_ptrを使うと、checked_delete でちゃんとコンパイルエラーが起きます。 おそらくshared_ptrは、参照カウンタを作るために、コンパイラがFooクラスの実体が作られるのが遅くなって、コンパイラがFooクラスの実体を作るときにFoo::stImplクラスを知っている状態になっているからでしょう。 ただし、shared_ptrでうまくいっているのは、あくまで、たまたまで、コンパイラを変えたりすればうまく行くとは限らないはずです。 正しい解決法は、上に書いたように、Foo に(空の)デストラクタを定義することです。

kary
質問者

お礼

ご回答頂きましたようにデストラクタを明示したら解決しました。ありがとうございました。また、もしこの件について触れられている書籍をご存じでしたらご紹介頂けないでしょうか。「プログラミングc++」「Effective c++」「Effective STL」「Efficient c++」「Efficient c++ style」「Modern c++」当たりを呼んで勉強していますが、もしかしたら見落としがあったかもしれませんし、別の書籍でしたら購入を検討してみたいと思っています。よろしくお願いします。

関連するQ&A

  • shared_ptr クラスについて

    shared_ptrクラスを使いたいのですが、使えません、どうしてでしょうか?ソースはこれです。 #include<iostream> #include <string> #include <fstream> #include<memory> using namespace std; class SMonster{ string name; int power; public: SMonster(); SMonster(int p); ~SMonster(){ }; void SetPower(int p); int GetPower(SMonster& t)const; void walk(const string& str); int GetPoint(void)const; }; class B {}; class D : public B {}; int main(void) { shared_ptr<D> sp0(new D); SMonster m(200); SMonster n(100); std::cout<<m.GetPower(m)<<std::endl; std::cout<<n.GetPower(n)<<std::endl; ShowWindow(10); }

  • boost::shared_ptr::getにて

    こんにちは。 C++で書かれたプログラムの保守をしています。 以下のような感じで書かれたクラスがあります。 class Foo { public :   Foo(){} ;   virtual ~Foo(){} ;   void Set( boost::shared_ptr< int > pValue )   {     _pValue = pValue.get() ;   } protected :   void* _pValue ; } ; このクラスから _pValue を再び boost::shared_ptr< int > にして取得するにはどうしたら良いのでしょうか? 強引に、 boost::shared_ptr< int > Get( void ) {   boost::shared_ptr< int >  temp ;   temp.reset( (int*)_pValue ) ;   return temp ; } とやっても案の定ダメでした。 void* _pValue の部分はいろいろ使われていて変更できません。 何かよい手段はないものでしょうか?

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

    プログラミング言語は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になることは確認 デストラクタが呼び出されました

  • C++の話です。

    C++の話です。 静的メンバ変数としてクラスを宣言した場合、デストラクタが呼ばれていないようなのですが、呼ぶ方法はありませんか? できれば「new」「delete」を使わずできると理想的です。 分かる方教えていただけると助かります。 以下、サンプルコードです。 「デストラクタが呼ばれました」と出力されない上、デバッガを使って試してみましたが、やはり呼ばれていないようです。 #include<iostream> class Test{   public:     ~Test(){       std::cout<<"デストラクタが呼ばれました"<<std::endl;     } }; class A{   private:     static Test T; }; int main(){   A a;   return 0; }

  • boostスマートポインタ(shared_ptr)を使用した、簡単なフ

    boostスマートポインタ(shared_ptr)を使用した、簡単なファイルの読み込みとループ処理で困っています。 以下に示すような、テキストファイルから情報を読み取って構造体のvectorをつくる処理をshared_ptrを使って行おうとしています。 input.txt-- >Taro /boy /8 years old >Hanako /girl /6 years old /likes candy ----- このファイルの'>'で始まる行は'名前'として、それ以外の '/ 'で始まる行はvectorに入れて、このふたつを個人ごとにまとめて構造体として管理するという形をとりたいと考えています。 つまり構造体の要素は <名前の行> <それ以外の行が入ったベクター> となります。 ファイル読み込みの過程で '>' を認識したらこれらのメモリ領域を確保し、shared_ptrで管理したいと考えています。 出来た構造体をベクターに入れ、ファイル読み込みのあとに出力する、という形でプログラムを以下のように書きました。  #include <string> #include <fstream> #include <iostream> #include <boost/shared_ptr.hpp> using namespace std; //構造体定義  struct my_struct{ std::string name;    std::vector<std::string> vector_of_string;  } ; int main(void){ std::ifstream ifs("input.txt"); //構造体を入れるベクター std::vector<my_struct> vector_of_my_struct; std::string buf; boost::shared_ptr<my_struct> SMARTptr_my_struct(new my_struct); boost::shared_ptr<vector<string>> SMARTptr_vector_of_string(new vector<string>); while(ifs && std::getline(ifs, buf)) { if(buf[0] == '>'){   //構造体の領域を作成 boost::shared_ptr<my_struct> SMARTptr_my_struct(new my_struct); //'/'で始まる行を格納しておくベクター領域の作成 boost::shared_ptr<vector<string>> SMARTptr_vector_of_string(new vector<string>); //構造体に作成されたベクターを入れる (*SMARTptr_my_struct).vector_of_string = *SMARTptr_vector_of_string; //構造体にこの行ををnameとして入れる (*SMARTptr_my_struct).name = buf; //ここで新たに作られた構造体をベクターに入れる vector_of_my_struct.push_back(*SMARTptr_my_struct); } if(buf[0] == '/'){ //この行をstring格納用のベクター(if(buf[0]=='>') ですでに作られている)に入れる (*SMARTptr_vector_of_string).push_back(buf); } この後、構造体のベクターのループから情報を出力しますが字数制限のため省略させていただきます。 input.txtと同じ内容が出力されることを期待したのですが、実際は'>'で始まる名前の行しか出力されません。 どうやらベクターへのpush_backができていないか、そもそもshared_ptrの扱いに根本的な間違いがあるのでは?と思っているのですが、原因がわかりません。環境はVC++2008 express edition, boost 1.38です。 解決方法、原因、アドバイスなど分かりましたらよろしくお願いします。

  • 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; }

  • クラスについての基本的な質問です

    こんにちは。 クラスを使ってプログラミングしていて、どうしたらいいかわからなくなってしまったので質問させてください。 1つのクラスを2つのクラスが継承している状態で、多重定義エラーが出てしまっています。 ---------- base.h class BaseClass { public: BaseClass(); ~BaseClass(); protected: class InnerClass { public: InnerClass(); virtual ~InnerClass(); } }; ---------- Aclass.h class AClass : public BaseClass { public: AClass(); ~AClass(); }; ---------- BClass.h class BClass : public BaseClass { public: BClass(); ~BClass(); }; ---------- 上記状態で、AClass.cpp、BClass.cpp内で AClass::InnerClass::~InnerClass() { } のようにコンストラクタやデストラクタ等を作っています。 BaseClass.cppにはInnerClassに関するものは書いていません。 「AClassのInnerClassのデストラクタ」というような書き方だと思うのですが、 BClass.o: multiple definition of 'BaseClass::InnerClass::~InnerClass()' AClass.o: previous definition here のように、後にビルドしたものがBaseClassについて多重定義になっているようです。 なぜ多重定義になってしまうのでしょうか? わかりにくいことがあったら言ってください。 補足等しようと思います。 よろしくお願いします。

  • ヘッダのインクルード時のエラー

    初歩的な質問で恐縮です。 以下のようなファイルの構成でc++のプログラムを組んでいます。 <header.h> class H { public: virtual void func() = 0; }; <fileA.cpp> #include "header.h" class A : public H { public: void func() {} }; <fileB.cpp> #include "header.h" class B : public H { public: void func() {} }; <main.cpp> #include "fileA.cpp" #include "fileB.cpp" int main() { A a; B b; a.func(); b.func(); return 0; } これをビルドしようとすると、「クラス"H"を再定義しようとしています」とエラーが出てコンパイルができません。理屈は分かるのですが、これの対処方法が分かりません。この場合、header.hはJAVAにおけるInterfaceのような役割としてそれを使うfileA.cpp、fileB.cppの両者にインクルードしておきたいのです。これはどのようにして対処すべきなのでしょうか?

  • C++/STLの動作

    お世話になっております。 クラスのインスタンス化時に自身のポインタをストックしようと下記を実行しましたが、 出力が0となり、予想に反していました。 そこで、"test.cpp"の3行目を"main.cpp"に移動すると正しく動作します。 これはどういった動作が原因で起こったのでしょうか? よろしくお願いします。 /// test.h /// #include <vector> class Test { public: Test(){list.push_back(this);} static std::vector<Test*> list; }; /// test.cpp /// #include "test.h" std::vector<Test*> Test::list; /// main.cpp /// #include <stdio.h> #include "test.h" Test test; void main() { printf( "%d\n", test.list.size() ); // "1"と出力されると思ったけど… }

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

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