CString派生クラスのデバッグ情報

解決


QR  2012-01-12 10:40:54  No: 73110  IP: 192.*.*.*

Visual Studio 2010でMFCを使用しています。
試験的にCStringとCWndとCSizeの派生クラスを以下のように用意してみました。

class CStringEx : public CString
{
public:
  CStringEx() {}
  virtual ~CStringEx() {}
};

class CWndEx : public CWnd
{
public:
  CWndEx() {}
  virtual ~CWndEx() {}
};

class CSizeEx : public CSize
{
public:
  CSizeEx() {}
  virtual ~CSizeEx() {}
};

CStringEx str;
CWndEx wnd;
CSizeEx size;

すると、デバッグウィンドウのローカルやウォッチで、
CStringExは{...}と表示されてしまい、
+を押してツリーを展開しないと中の文字列が確認できなくなってしまいます。
CStringをそのまま使うと、ちゃんと最上位で""と表示されます。

CWndExやCSizeExは、なにもしなくても基本クラスと同じように
{CWndEx hWnd=0x00000000}や{cx=0 cy=0}と表示されます。

これはデバッグ機能の制限になってしまうのでしょうか?
それとも、なにかデバッグ用にオーバライドするべきものがあるのでしょうか?

編集 削除
tetrapod  2012-01-12 16:45:49  No: 73111  IP: 192.*.*.*

単に CString の真の型名が長すぎるので無操作では表示されないだけっぽい。
struct this_is_a_very_very_long_named_structure_for_test {
  const char* p;
};
この長い名前のクラスの変数は無操作で中身が表示され、
struct derived : this_is_a_very_very_long_named_structure_for_test {
};
derived な変数はクリックしないと中身が表示されない。
ということで [デバッグ機能の制限] に1票。

本題とは無関係だけど
CString や CSize は派生させて使うことを想定していないクラスなので
(polymorphic に使う設計になっていない)
このような使い方をすると polymorphic に動く保証は無いよ。

特に提示例 CStringEx は CString の良さを殺してしまっているのでおいしくない。

編集 削除
QR  2012-01-13 09:35:17  No: 73112  IP: 192.*.*.*

> 単に CString の真の型名が長すぎるので無操作では表示されないだけっぽい。

そういうことでしたか。
同じソースをVC6で試してみたところ、先頭の階層でも{""}と表示されました。
2010のデバッガの仕様として諦めるしかなさそうです。
解析いただきありがとうございます。

> CString や CSize は派生させて使うことを想定していないクラスなので
> (polymorphic に使う設計になっていない)
> このような使い方をすると polymorphic に動く保証は無いよ。

参考までに伺いたいのですが、下のページのFindNoCase()などのように、
「便利な機能を追加したCString」というものを作りたいのですが、
派生するのがよくないとなると、どのような方法があるのでしょうか?
http://www.codeguru.com/cpp/cpp/string/ext/article.php/c2793

編集 削除
tetrapod  2012-01-13 12:26:29  No: 73113  IP: 192.*.*.*

CString は const TCHAR* とほぼ互換になるように実装面で小細工してあるクラス。
これを拡張し正規の polymorphism に対応すべく virtual を使うと、
暗黙の vptr メンバが増えてしまい const TCHAR* の代わりに使うことができなくなる。

提示 codeguru の CStringEx は virtual が無いので vptr も増えず、
そのため CStringEx → const TCHAR* の変換ができるので問題が少ない。
その代わりあくまで CStringEx として使わねばならない。

polymorphism はあきらめて virtual なメンバ関数を一切使用しない、
のであれば派生させて問題ないよ。

編集 削除
QR  2012-01-14 09:37:01  No: 73114  IP: 192.*.*.*

> polymorphism はあきらめて virtual なメンバ関数を一切使用しない、
> のであれば派生させて問題ないよ。

デバッガで今回のCStringExの実体を見てみたところ、
__vfptrがCStringEx自身の階層に表示されていました。
CStringExのデストラクタを消してみたところ、どの階層にも出ませんでした。

もう少しおつきあいいただきたいのですが、これはつまり、
CStringExのデストラクタをvirtualにしておくこと自体、
無意味(むしろまずいこと?)ということになりますでしょうか?

編集 削除
tetrapod  2012-01-14 19:44:12  No: 73115  IP: 192.*.*.*

そういうのは目的に応じて取捨選択するもんだ。
C++ ってそういう言語。

CStringT は派生させないことを前提に設計されているクラスであり、
それを派生させようとしているのだからどこかに無理が発生する。
何をしたいか、によって別の何かを切り捨てるだけのこと。

CString を polymorphic に使うことが目的なら const TCHAR* との互換性を捨てて virtual を書く
const TCHAR* との互換性が目的なら polymorphic を捨てる

編集 削除
QR  2012-01-14 20:58:15  No: 73116  IP: 192.*.*.*

> CString を polymorphic に使うことが目的なら const TCHAR* との互換性を捨てて virtual を書く
> const TCHAR* との互換性が目的なら polymorphic を捨てる

了解しました。ありがとうございます。
とりあえずは、現在の実装で困った問題は発生していないので、
このままの方法で続けてみたいと思います。

編集 削除
tetrapod  2012-01-15 00:00:44  No: 73117  IP: 192.*.*.*

まあ polymorphic に使おうとしても使えないんだけどね・・・
状況としては下記と同じなので CStringEx を CString として扱うことはできない。
例題の CStringEx * を CString * にアップキャストしてはいけないので
最初から指摘しているとおり polymorphic に使ったらバグる。

struct b {
  b() { std::wcout << L"b::b\n"; }
  // virtual つけたりはずしたりして味噌
  /*virtual*/ ~b() { std::wcout << L"b::~b\n"; }
};

struct d : b {
  d() { std::wcout << L"d::d\n"; }
  ~d() { std::wcout << L"d::~d\n"; }
};

int wmain(int argc, wchar_t* argv[]) {
  b* p=new d;
  delete p;
  return 0;
}

最初の発言にある CStringEx の派生クラス CStringExEx を作ったら
CStringExEx を CStringEx として使うことはできる
CStringExEx を CString として使うとバグる
(codeguru の CStringEx は以下略)

編集 削除
QR  2012-01-18 09:09:17  No: 73118  IP: 192.*.*.*

サンプルソースありがとうございます。
提示していただいた仮想デストラクタの動作は理解しているつもりですが、
CStringExをnewで動的に作成したり、
そのCStringEx*をCString*でdeleteする使いかたはしていないので、
このような問題を気にしたことはありませんでした。

編集 削除
tetrapod  2012-01-18 10:46:48  No: 73119  IP: 192.*.*.*

自分ひとりで作って使う分にはそういう *問題ある使い方* をしないんだろうが
自作クラスを他人に公開したときには、他人が *問題ある使い方* をしない保証がないので、
・どんな使われ方しても問題ないクラスを作る努力をするか(作り手側責任)
・ドキュメントに明記して注意喚起するか(使い手側責任)
今回の話なら後者かな。

編集 削除
QR  2012-01-18 22:03:08  No: 73120  IP: 192.*.*.*

> ・どんな使われ方しても問題ないクラスを作る努力をするか(作り手側責任)
> ・ドキュメントに明記して注意喚起するか(使い手側責任)
> 今回の話なら後者かな。

そうですね。
「CStringExをCString*でdeleteする」というようなことをしなければ、
今回のCStringExはいろいろ追加機能を使えて便利になるはずなので、
注意書きを入れておこうと思います。
ありがとうございます。

編集 削除