class CItem {…};
CItem* m_pItem = new CItem;
return m_pItem;
このあとに、returnされたアドレスだけを使って
pの指す先の実体がdeleteされずに残っているかを
任意のタイミングで確認する方法はあるのでしょうか?
それとも、CTypedPtrListのようなものを使って、
ポインタを一括管理するようなクラスを用意するべきなのでしょうか?
生ポインタをそのまま return するってのは現代 C++ 的には時代遅れで、
shared_ptr とかに wrap するのがいいんぢゃないかな。
> それとも、CTypedPtrListのようなものを使って、
> ポインタを一括管理するようなクラスを用意するべきなのでしょうか?
一括管理という用語の意味が俺には通じていないんだが、目的次第。
delete し忘れを中央集権方式デバッグ補助で見つけたいとかそういう意図?
俺ならたぶんやらない。
shared_ptr とかにて代用して 生 delete を排除する方向に持ってくとおもう。
delete をそもそも書く必要がなくなるので、書き忘れもなくなる。
> 任意のタイミングで確認する方法はあるのでしょうか?
あるかないかと言えば、ないです。
確実に確認する方法というのは存在しないんじゃないかなぁ。
運用方法を想定し、結果的にというならいくつかありそう。
もっとも簡単なところでは、deleteしたら必ずNULLを代入する約束にしておけば、NULL
チェックだけで済みます。
また、ご自身もおっしゃっているように、他のクラスにポインタの管理を任せてしまう
のも一つの手です。
実際、私はテンプレート型のリストクラスにポインタを一括管理させており、最近はほ
とんどdelete文を書いていません(リストクラスのデストラクタにて自動delete)。
いずれも運用によるものですから、運用方法を守らない人がいたらダメですね。
> このあとに、returnされたアドレスだけを使って
> pの指す先の実体がdeleteされずに残っているかを
> 任意のタイミングで確認する方法はあるのでしょうか?
単なるアドレス数値の指し示す先が有効なオブジェクト
であるかどうかを調べる簡単な手段はありません。
この場合「delete オペレータを公開しない」など、
不用意にdeleteされないことを目指すべきかもしれません。
そうすればポインタは常に有効であることを「ある程度」
保障できます。
tetrapodさん、aetosさん、bunさん、仲澤@失業者さん、
解説ありがとうございます。
ツリー構造の情報を内部で持っているのですが、
Windowsのツリービューのアイテムなんかは
_TREEITEMという内部構造体のポインタをHTREEITEMとして返しているようです。
これと同じような感じで、内部でnewで確保したオブジェクトのポインタを
戻り値として返しているのですが、それを再度引数に取る別関数の中で
「指定されたアドレスはちゃんと内部にオブジェクトが存在しているものか」
をチェックしておきたいのです。
もちろん、内部ではポインタを見失うような構造にはなっていないので、
ツリーのように先頭から一つずつ階層を行き来して探していけば
判断自体はできるのですが、関数が呼ばれるたびに探し直していくことになり、
オブジェクトの数や呼ばれる回数が増えてくると応答時間も無視できなくなるため、
渡されたポインタを見るだけで「存在するオブジェクトのアドレスか」を
判断できないものだろうかと思って質問させていただきました。
やはりアドレスを見るだけでは断定できないのですね。
ということは、ツリービューもSetItemText()などの際には
HTREEITEMの有効性を内部で探索しているということでしょうか。
> deleteしたら必ずNULLを代入する約束にしておけば
ちなみに、この方法の場合、どこにNULLを入れるのでしょうか?
内部からは、deleteするオブジェクトのポインタ自体は
誰がどこに持ち続けているかはわからないのではと思うのですが。
> ちなみに、この方法の場合、どこにNULLを入れるのでしょうか?
いまいち、何がしたいのかが分からないのですが、
> CItem* m_pItem = new CItem;
> return m_pItem;
における、m_pItemは m_ がついていることからして、何らかの
クラスのメンバを想定しているのかな?
だとしたら、
> return m_pItem;
の戻り値を、CItem* では なく、CItem*& にすれば、外側から内部の
値を変更できます。
が、あまりお勧めできません。
クラスによる隠蔽効果が薄れますから。
メンバであるなら、deleteは特定のメンバ関数かデストラクタに任せ
た方が良いと思います。
> これと同じような感じで、内部でnewで確保したオブジェクトのポインタを
> 戻り値として返しているのですが、それを再度引数に取る別関数の中で
> 「指定されたアドレスはちゃんと内部にオブジェクトが存在しているものか」
> をチェックしておきたいのです。
ポインタを外部が持っている間に、内部でポインタを無効にする可能性があるという
ことでしょうか?外部がポインタを長期間持たないようにするのがベストかと。
外部が何らかの操作でポインタを必要とするときに、内部の関数を呼び出して
ポインタを取得し、操作終了後は外部はその値を管理しない。
常に最新の情報を内部から取得するわけです。
以上の方針が基本だと思いますが、効率面でどうしてもというならば、
トリックを考えましょう。1つ思いついたものを書きますが、亀山さんの
問題の解決方法としてふさわしいかは分かりません。
class Piyo;
class PiyoWrapper {
Piyo *piyo_;
bool isValid_;
public:
PiyoWrapper() {
piyo_ = new Piyo();
isValid_ = true;
}
void DeletePiyo() {
delete piyo_;
piyo_ = NULL;
isValid_ = false;
}
~PiyoWrapper() {
delete piyo_;
}
};
class Hoge {
std::vector<PiyoWrapper> wrapper_;
};
内部クラスがHogeです。
内部クラスがPiyoを作るときに、PiyoWrapperとして作り、
Hogeの寿命を通してPiyoWrapperのインスタンスは残します。
各クラスのアクセサ等は適当に追加・変更してください。
外部はPiyoWrapperへのポインタを内部からもらいます。
Hogeの寿命を通してPiyoWrapperが有効であることは保証されていて、
isValid_でPiyoの有効性を判断します。
> Windowsのツリービューのアイテムなんかは
> _TREEITEMという内部構造体のポインタをHTREEITEMとして返しているようです。
ここには疑問を挟む余地があります。
commctrl.h の中で
typedef struct _TREEITEM *HTREEITEM;
と定義されていることを指しているのだと思いますが、実際に Windows OS のソースコー
ド中でも、HTREEITEM が _TREEITEM 構造体へのポインタであることは保証されません。
ひょっとしたら内部的には、ポインタではなく、単なる整数値かもしれません。
ではなぜ commctrl.h ではこのように定義されているのかと言うと、推測ですが、こうし
ておけば HTREEITEM 型の値を HWND 型に代入するようなコードを書いた場合、コンパイ
ルエラーにすることができるというメリットがあるためかもしれません。
typedef DWORD_PTR HWND;
typedef DWORD_PTR HTREEITEM;
では、エラーにすることができませんからね。
> これと同じような感じで、内部でnewで確保したオブジェクトのポインタを
> 戻り値として返しているのですが、それを再度引数に取る別関数の中で
> 「指定されたアドレスはちゃんと内部にオブジェクトが存在しているものか」
> をチェックしておきたいのです。
関数を作る側と、それを使う側には、約束事が存在します。
引数にポインタを取る関数であれば、そこに delete 済みのポインタや、ポインタと偽っ
た整数値を渡さないというのは使う側の約束であり、有効なポインタを渡されたからには
きちっと動作するというのは作る側の約束です。
基本的には、その約束は破った方がペナルティを負うべきなので、delete 済みのポイン
タを渡した結果、アプリが落ちてしまっても、それは呼び出し側が悪いので、関数側でフ
ォローすべきことではないと思います。
わき道雑談
> ポインタを外部が持っている間に、内部でポインタを無効にする可能性がある
CWnd::GetDlgItem, GetWindow, その他がこれだなぁ
いつ無効になるのか MFC ソースをちまちま追っかけたなぁ (遠い目)
ちなみに commctrl.h を見ると
#define TVI_ROOT ((HTREEITEM)0xFFFF0000)
とかあるのでそもそも原理的にチェックできない、と判断しますな
読み返してみて、何となく分かった気がします。
クラス内でnewしたオブジェクトのポインタを外部に引き渡してしまうと、
外部でdeleteされないか心配ということでしょうか?
そういうことなら気持ちは分かりますが、どうしようもない気がします。
だって、以下のようなコードもありですから、
int* CTest::Func()
{
return &m_i;
}
これで、戻り値 delete されたら、その時点でオジャンです。
こんなの責任もてません。
そんなわけで、私も aetos さんの意見に賛成です。
> 基本的には、その約束は破った方がペナルティを負うべきなので、...
bunさん、たいちうさん、aetosさん、tetrapodさん、解説ありがとうございます。
> クラス内でnewしたオブジェクトのポインタを外部に引き渡してしまうと、
> 外部でdeleteされないか心配ということでしょうか?
それもありますが、今回の件は
「クラス内ですでにdeleteしたオブジェクトのポインタを外部から渡されたことを、
そのポインタだけで判断できないか」ということでした。
Windowsのツリービュー(CTreeCtrl)の場合、
HTREEITEM hItemParent = m_tree.InsertItem(_T(Parent));
HTREEITEM hItemChild = m_tree.InsertItem(_T(Child), hItemParent);
m_tree.DeleteItem(hItemParent);
m_tree.SetItemText(hItemParent, _T(SetItemText));
m_tree.SetItemText(hItemChild, _T(SetItemText));
のSetItemText()で親も子もちゃんとFALSEが返ってきます。
ハンドルにはInsertItem()の戻り値が入ったままです(すでにゴミ)。
これと同じような挙動にしておきたいなと思い、
アイテムを更新したり削除したり子を追加したりするような関数すべての内部で
「念のためまず今の階層を行き来して存在自体をチェック」していたのですが、
応答時間などの問題が出てきたため、
渡されたアドレス値そのものから一発判定できないかなと思いました。
たしかに、こんな呼びかたは実際の使用では想定外ですし、
そこまで考慮してやるものでもないのですかね…。
> これと同じような挙動にしておきたいなと思い、
これは多分「削除予約」型の動作かと思います。
DeleteItem( h)で、「h」の意味するオブジェクトを
リアルに削除せず、「h」のメンバの「削除予約フラグ」を
立てるだけで済まします。このオブジェクトはメモリー上には
存在しますが、論理的に削除済みなわけです。
そうでないと、「h」に対応するオブジェクトがリアルに
存在しないことを証明するため、配列内の全オブジェクトを
トレースしなければならず、不経済です。
こう言っちゃなんですが、これではちょと頭が悪いかも(笑)。
当然、ガベージコレクションは別の機会に行わなくてはなりません。
> これは多分「削除予約」型の動作かと思います。
これもかなりひねくれた例ですが、
m_tree.SetItemText((HTREEITEM)0x12345678, _T(SetItemText));
というように、適当なハンドル値を入れてもちゃんとFALSEが返ってきます。
これも含めて真似るために、「頭が悪いかも」のやりかたをしていたのですが、
削除予約型のときは、このようなメモリ上に存在しないもののときに
正しくエラーを返すことができなくありませんか?
もちろん、ツリービューがHTREEITEMをどのように扱っているのかは
aetosさんの言われるように不明なので、実際には内部を行き来せずに
判定できる方法を備えているのかもしれませんが、常識的にはこのような場合、
削除されたアイテムや存在しないアイテムを指定されたときは
わざわざ丁寧にFALSEを返してやらなくてもいいものでしょうか?
ハッシュテーブルのように、全数探索しなくても高速に、対象のアイテムがあるかどうか
判断できる方法はありますけどね。
> 常識的にはこのような場合、
> 削除されたアイテムや存在しないアイテムを指定されたときは
> わざわざ丁寧にFALSEを返してやらなくてもいいものでしょうか?
常識ねぇ…。
個人的には、変な値を渡した時に例外出して落ちちゃっても、常識はずれだとは思いませ
んけどね。