- ベストアンサー
C++ 多態とstlのコンテナについて
以下のように、継承関係を作ります。 --------------------------------------- #include <iostream> #include <list> #include <vector> using namespace std; struct Base { virtual ~Base() = 0; }; Base::~Base() {} struct Sub1 : Base { int v; Sub1(int i) { v = i; } }; struct Sub2 : Base { double v; Sub2(double d) { v = d; } }; --------------------------------------- この場合、 Sub1, Sub2 のインスタンスをなにかコンテナに入れたい場合は、一般的には以下のように書けばいいのでしょうか? ---------------------- list<Base*> l; vector<Base*> v; Sub1 s1(3); Sub2 s2(4.4); l.push_back(&s1); l.push_back(&s2); v.push_back(&s1); v.push_back(&s2); ---------------------- list<Base>, vector<Base>も試しましたが list<Base> は宣言したところで vector<Base> は push_back() したところで コンパイルエラーになりました。 これは、こういうものなのでしょうか? むしろ、struct(もしくはclass)の書き方を変えたりすれば、問題なくなったりするのでしょうか? 全体としては、C++は参照などあって、どいう場合にポインタ使うべきなのかそういう部分に混乱しているような気もします。 いろいろ質問してしまって、申し分けないですがなにか ひとつでも答えられるものがあれば回答してもらえると ありがたいです。
- みんなの回答 (7)
- 専門家の回答
質問者が選んだベストアンサー
> これは、こういうものなのでしょうか? そういうものです。vector<Base>で格納しようとすると、Baseのインスタンスを作ろうとするのでコンパイルに失敗します。 書き方をかえるという意味ではBaseを純粋仮想でなくすればコンパイルは通りますが、 (Baseを実体化するので)Sub1やSub22のポリモフィックな動作はせず、 「スライシング」と呼ばれる問題を引き起こします。(コンパイルが通るだけで通常は解決策ではありません) また、一般にポインタを格納する場合、new等をすることになりdeleteの問題が出ますが、 「STLコンテナにはstd::auto_ptrは使えない」仕様です。 例外はあるものの、多くの場合boost::shared_ptrのような「スマートポインタ」を使うとすっきりします。 std::list<boost::shared_ptr<Base> > l; std::vector<boost::shared_ptr<Base> > v;
- 参考URL:
- http://www.boost.org
その他の回答 (6)
- MrBan
- ベストアンサー率53% (331/615)
list<Base> l; vector<Base> v; このあたりを「定義」すると、少なくともVC7.1ではエラーになりますが(多分VC8等も一緒?)、 確かに「宣言」だけでの警告は難しいでしょう。 # 宣言だけならエラーとは言えないはず。
お礼
やっぱりSTLによって違うんですね。 わざわざ調べていただいてありがとうございます。
- Tacosan
- ベストアンサー率23% (3656/15482)
X のサブクラスとして Y を宣言した場合, クラスY のオブジェクトを「X* あるいは X&」で使えば Y のメンバ関数が呼び出されますが, 「X」で使うと X のメンバ関数しか呼び出せません. 「クラスのオブジェクトそのもの」として使うのか, それとも「ポインタや参照」で使うかということで明確に区別する必要があります. で, vector<Base> の時点でエラーにすべきだとは思うんだけど.... 実装上, 難しいんじゃないかなぁ? 少なくとも, 「宣言した時点」でエラーにするのは困難だと思う. list<Base> は多分内部的に Base を作ろうとしてエラーになるけど, vector<Base> は「Base を直接作る」とは限らない (この辺, デフォルトコンストラクタが要求されていないことと関連します) ですから. バルクでメモリをもらってきて「必要に応じてコピーコンストラクタを呼び出す」という実装かもしれないですし, このような実装でも規格上問題ありませんから.
お礼
> 「クラスのオブジェクトそのもの」として使うのか, それとも「ポインタや参照」で使うかということで明確に区別する必要があります. ANo.1のスライシングの問題ですね。(昨日覚えた事なので自信ないですが。) 半端にコピーされること自体は多分問題ないけど、 Yのメンバ関数で、XにないY独自の領域にアクセスしようとすると困るので多分そうなっているんだろうと考えています。 > 少なくとも, 「宣言した時点」でエラーにするのは困難だと思う. コンストラクタにプリント入れて list<Base>, vector<Base> をやってみましたが、コンストラクタは動いていない様子でした。 listの方は、番兵かなにか作っていてそいういう部分で困るのかもしれないですね。(完全に勘です。STLのソースは良く分かりませんでした。) ありがとうございました。
- MrBan
- ベストアンサー率53% (331/615)
# 休出とかするものじゃない…今日はメタメタです。申し訳ない。 > vector<Base>を作る時点/格納の時点ということで言えば、作る時点でエラーになります。 これは、Baseが純粋仮想関数を持っていると、push_backなどしなくても list<Base> l; vector<Base> v; だけでエラーになります(なるべきです) > コピーコンストラクタではないかと思ったのですが、これも違いますか? ごめんなさい。こっちはコピーコンストラクタの間違いでした…orz
お礼
> これは、Baseが純粋仮想関数を持っていると、push_backなどしなくても > list<Base> l; > vector<Base> v; > だけでエラーになります(なるべきです) そうですね、どうせ push_back などできないなら、宣言の時点でエラーにしてくれた方が親切です。 ただ、そうならないのは僕が多分そういう実装の STL を使っているということなんでしょうね。 ちなみに僕は gcc4.1.1 で libstdc++6.0.8 を使っています。 ありがというございました。
- jacta
- ベストアンサー率26% (845/3158)
この手の問題をクリアするには、既に回答が出ているように、boost::shared_ptrあるいはstd::tr1::shared_ptr(可能であれば後者の方が望ましいかも)を使うのが正攻法だと思います。shared_ptrを使えば、仮想デストラクタがなくても、うまく解体できるようになりますし、いろいろメリットがあります。 サイズ効率がどうしても気になるのであれば、boost::ptr_vectorという選択肢もあるにはあります。使い勝手がいまいちなので、どちらかといえば、それほどお勧めはしませんが...。
お礼
> std::tr1::shared_ptr これも #include <tr1/memory> で使える様子でした > shared_ptrを使えば、仮想デストラクタがなくても、うまく解体できるようになりますし、いろいろメリットがあります。 この、shared_ptrは今日知ってリファレンスカウントをもっているというのが分かったのですが、それ以外にも機能があるのですね。 http://www.zeroscape.org/cgi-bin/wiki/wiki.cgi?page=shared_ptr+minor+features 多分ここに書いてあるような事だと思いますが virtual でないデストラクタしかなくても、よきにはからってくれるようでした。 実装方法は全く検討もつきませんが、凄いことは分かりました。 ありがとうございました。
- Tacosan
- ベストアンサー率23% (3656/15482)
vector とか list とかのコンテナクラスに入れるためには, コピーコンストラクタが必要です. デフォルトコンストラクタはなくても動作することはすると思うけど.... もっとも, 仮想関数があるのでポインタを使わないと予定した動作はしないと思いますが.
お礼
やっぱり、コピーコンストラクタが必要なのですか。 > もっとも, 仮想関数があるのでポインタを使わないと予定した動作はしないと思いますが. これは、http://www.aerith.net/design/argument-j.html にあるような予期せずスーパークラスの関数が呼ばれてしまう、ということでしょうか? 僕が調べたところ、スーパークラスの関数が呼ばれる場合は直接スーパークラスの関数が呼ばれていて、ポインタ経由でうまくいっている場合は仮想関数テーブル経由になっているみたいでした。 これが、コンパイラなどに依存することのかは分からないですが
- MrBan
- ベストアンサー率53% (331/615)
> なるほど、vector<Base>を作ろうとしたときでなく、 > 格納しようとした時点でインスタンスを作るのですか。 すみません。言葉が悪かったです。 vector<Base>を作る時点/格納の時点ということで言えば、作る時点でエラーになります。 # (内部的な動作に必要なので)デフォルトコンストラクタで生成できないオブジェクトは格納できません。 > あい変らずauto_ptrの存在意義は分からないのですが 動的生成したオブジェクトを関数の戻り値で返して、かつ解放を強制したい、とか。 ポリモフィックに動作させるために動的にnewしたいけど、スコープを抜けたら自動で解放したいとか。 例外がおきても安全にオブジェクトが解放されるようにしたいとか。 まぁ、スタック上のオブジェクトでカバーできない動的なオブジェクトを、 お手軽にスタック上にあるかのような使い方をするためのものだと思いますが。 # C++の場合、他言語のようにオブジェクトがデフォルトで参照というわけではないので、 # 「参照にした」際に発生する手間の軽減策(のひとつ)がauto_ptrかなという気はします。
お礼
> vector<Base>を作る時点/格納の時点ということで言えば、作る時点でエラーになります。 > # (内部的な動作に必要なので)デフォルトコンストラクタで生成できないオブジェクトは格納できません。 vectorの場合は、push_back()の段階で、コンパイルエラーだったので格納するときのいうのは納得がいっていたのですが、違うのですか? また、コピーコンストラクタを作って、その中で cout<< でプリントを入れるとpush_back()したときに、それ(コピーコンストラクタ)が動いているようだったのでデフォルトコンストラクタではなく、デフォルトコピーコンストラクタではないかと思ったのですが、これも違いますか? デフォルトコンストラクタだと、引数付きコンストラクタなどを1つでも定義したclass,structだとvector,listが作れなくなると思ったのですが。 auto_ptrについては、用途が思いうかびました。 ファクトリーパターンなど、ポインタでしかインスタンスの操作が 想定されてない場合は、必要かもしれないと思いました。 ありがとうございました。
お礼
> そういうものです。vector<Base>で格納しようとすると、Baseのインスタンスを作ろうとするのでコンパイルに失敗します。 なるほど、vector<Base>を作ろうとしたときでなく、格納しようとした時点でインスタンスを作るのですか。 > 書き方をかえるという意味ではBaseを純粋仮想でなくすればコンパイルは通りますが、 ああ、これもそういえばやっていたのですが、インターネットの文章など見るとやっぱり、 純粋仮想関数など使っている様子だったので、なにか問題があるのかと思っていたのですが、そういう問題があるのですね。 非常に参考になりました。 > また、一般にポインタを格納する場合、new等をすることになりdeleteの問題が出ますが、 > 「STLコンテナにはstd::auto_ptrは使えない」仕様です。 > > 例外はあるものの、多くの場合boost::shared_ptrのような「スマートポインタ」を使うとすっきりします。 こういうものがあるのですね。 スコープから外れたら、スタックに取っているオブジェクトのデストラクタ呼ばれるので auto_ptr って意味あるのかな?と思ってたのですが。 shared_ptrというのを使えばいいのですね。 (あい変らずauto_ptrの存在意義は分からないのですが) でも、shared_ptrも大きいか小さいか分かりませんが、コストがあるようなのでよく考えて、使うべきところで使うようにしたい思います。 ありがとうございました。