• ベストアンサー

C++クラスのconstメソッドの影響について

VisualC++2008ExpressEditionにてプログラミングしています。 C++のconstメソッド(関数)の基本的な働きについては理解しているつもりです。 次のようなキャッシュされたテクスチャを取り出すクラスメソッドがありまして、メインループの中でテクスチャを表示したいときに呼び出されている、とします。 const LPDIRECT3DTEXTURE9 Renderer::getCachedTexture(const string& name) { if (_cachedTextures.find(name) != _cachedTextures.end()) { return _cachedTextures[name]; } return NULL; } このRenderクラスはメインループからは参照経由で、texture = render.getCachedTexture(name)のような形で利用されています。 実はこのままのメソッドで利用するとなぜかメモリリークするのですが、const LPDIRECT3DTEXTURE9 Renderer::getCachedTexture(const string& name) const; のようにconstメソッドにするとリークしないことを確認しました。_cachedTexturesはPocoというライブラリの Poco::HashMapクラスなんですが、簡単にいうとstd::map<string,LPDIRECT3DTEXTURE9>インターフェースです。ちなみに、constの有無に関わらずメソッドの実装はかえずともコンパイルでき、実行できます。変わるのはconstが無いとリークする、有るとしない、ということだけなのですが、constの有無で実装を変えることができるのでしょうか?(_cachedTexturesの find()メソッドの実装内容が変わる?) ちなみに、_CrtSetDbgFlag()を使ってアプリ起動から終了までのダンプを取ったりしてみましたが、const有り無しでは変わらず、いずれも全体ではリーク無しで終了できます。つまり、アプリを長期間実行したままにしておくと溜まってしまう状況で、それがこのメソッドのconst無しのときだけ発生するので、どのようなメカニズムで生じているのか、知りたいと考えています。尚、メモリリークについてはメインループにて、1ループ毎に GetProcessMemoryInfo()のPROCESS_MEMORY_COUNTERS.WorkingSetSizeにて確認しています。

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

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

とりあえず Poco のソースを拾ってきてちょろっと読んでみましたが, 少なくとも今のものについてはリークしていないようにも見えます. まあ, 私の目は節穴だったりするのでよく見落としますが. でも, 意味的には「find でリークする」ことは考えにくいんだよね.... 簡単にできる (かもしれない) こととしては, このあたりでしこたまログを吐くくらい? いずれにしても return _cachedTextures[name]; は無駄っぽいので, その前の _cachedTextures.find(name) で得られるイテレータを覚えておくとよいでしょう. あと, 「const の有無」は「シグネチャの違い」と認識していいと思います. C++ では「引数の数やその型」が違えば同名の関数を 1つの名前空間で同時に定義することができるのはご存じだと思います. この「引数やその型」を「シグネチャ」と呼びます. つまり, 「シグネチャが違えば同じ名前の関数を定義することができる」わけです. で, (static でない) メンバ関数には「どのオブジェクトを通しで実行されたか」という情報も this ポインタとして与えられるわけですが, これは実質的に「関数に対する引数」として作用します. そして, 「const と宣言されたかどうか」によって this ポインタの型が変わります (クラス T のメンバ関数において, const なメンバ関数なら this の型は const T *, const でなければ T *). したがって, 「const かどうか」は「シグネチャの違い」とみなすことができるので「const なメンバ関数と const でないメンバ関数」は同時に定義できます. もちろんその実装も変えることができます (が意味は同じであることが普通... というか, 意味まで違うと使う人が混乱する). なお, シグネチャには「返り値の型」は含まれないので, 「返り値の型だけが違う」関数は宣言できません.

loopsketch
質問者

お礼

丁寧な回答ありがとうございます。

loopsketch
質問者

補足

私も調べてみました。どうやらoperator[]でのアクセスですとリークするようです。ですので、_cachedTextures[name]を_cachedTextures.find(name)->secondにしたところ、リークしなくなりました。find()などはTacosanのおっしゃるとおり、特に気になるところはなかったので、Tacosanの回答のような実装にしておこうと思います。Poco::HashMapのoperator[]もやはりconst有り無しがあり、const版の方は特に気にならなかったのですが、無し版の方はちょっと気になる感じだったので、もうちょっと調べてみます。

その他の回答 (3)

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

確認しました. 確かにバグです. ただし, operator [] のバグではありません. ここで使っている手法は 「sentinel (番人, 番兵などと訳す)」というもので, これを使って「要素がな かったときに追加する」というコストを削減しています (実際にはあまり減りま せんが). 内容を完全に理解したわけではないので具体的に「ここ」とは言いきれないので すが, おそらくバグがあるのは HashMap::opeartor [] の内部で呼び出している LinearHashTable::insert です. LinearHashTable::insert は最初に LinearHashTable::split を呼び出していま すが, その結果として「insert を呼ぶたびにハッシュ表が (1つずつ) 大きくな る」ことになります. ところが, 実際に insert で要素が追加されることはない (と思っていい) ので, 結果として「表が無駄に大きくなっている」といえます. これを「リーク」と呼んでよいかどうかはちょっと微妙.

loopsketch
質問者

お礼

回答ありがとうございます。私もそこかなと思いまして、質問のconstの有無が直接影響してはないな、ということもわかりましたので、締め切らせていただきました。ご協力ありがとうございました。 参考までに、このメソッドは1/60秒当たり3~5回呼ばれているので、1秒で30回ほど呼ばれることになります。実際に実行していて、1分弱で1MBくらい増えていて、数時間で落ちてしまっていました。24時間365日動作させたいアプリケーションなので、1バイトでも無駄にしたくなかったもので。 いずれにせよ、明確に原因がわかりましたので、完全な対処ができました。ありがとうございました。

回答No.2

>constの有無で実装を変えることができるのでしょうか? 「実装を変えることができるかどうか?」と言う質問の答えは「出来る」です。 以下の話は「コンパイラに依存し、コンパイル環境により異なる話」としてお読み下さい。 1つのクラスに「同じメソッド名のメソッド」が複数存在出来るのはご存知ですよね? 例えば void func1(void); void func1(int arg1); int func1(int arg1,String arg2); とか、戻り値や引数の個数が違うなら、同名のメソッドを定義できます。 これは「ユーザーの目に触れない、内部的な関数名が異なる」からこそ、可能なのです。 あるコンパイラでは、上記の3つのメソッドの「関数の実体の名前」は、以下のようになります。 ・ソースでの表記 void func1(void); void func1(int arg1); int func1(int arg1,String arg2);     ↓ ・関数の実体の名前 ClassName.VSD.func1@V ClassName.VSD.func1@I4 ClassName.I4SD.func1@I4SU このように「戻り値や引数の個数が違う同名のメソッドを定義すると、関数の実体の名前が異なってコンパイルされる」のです。 これは int func2(void); const int func2(void); のように「constの有無」でも同じで「constが有るのと無いのでは、関数の実体の名前が異なる」のです。 言い換えれば「関数の実体の名前が異なるので、constが有るのと無いのが、同時に存在出来る」と言う事であり、つまり「constの有無で実装を変えることができる」と言う事です。

loopsketch
質問者

お礼

丁寧な説明ありがとうございます。 実際にPoco::HashMapも確認してみたところ、find()やend()もそれぞれconst有り無しの実装がありました。

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

えぇと.... 「メモリ使用量の増加」は「メモリリーク」に直結しないんだけど, その辺は理解できていますでしょうか? あと, その _cachedTextures というのは Renderer のメンバーなんでしょうか? また, Poco::HashMap クラスには const な find と const でない find の両方があったりしませんか? もしそうなら, Renderer::getCachedTexture(const string& name) が const かどうかで呼び出す find が違うので, そこでなにかがあるのかもしれません.

loopsketch
質問者

補足

2~3時間するとメモリを使い切ってヒープが壊れてる、みたいなダイアログが出てアプリが落ちてしまうので、メモリリークと判断しています。 _cachedTextures は Renderer のprivateなメンバーです。また、確かに、Poco:HashMapにはconstのfind()/end()とそうでないfind()/end()がありました。試しに存在チェックのところをなくしていきなり_cachedTextures[name]を返すようにしてみましたが、それでも同様にconstを外すとメモリが増えていくようです。