オブジェクトへのポインタは保存できないのでしょうか

[戻る]
はまち 2002/04/30(火) 23:30:47
////////////////////////////////////////////////////////////////////////////////
    
    開発環境 : Visual C++ 6.0
    動作環境 : Windows 98 Second Edition
          MFC使用  MDIベース

////////////////////////////////////////////////////////////////////////////////

シリアル化についてお聞きしたいのですが、MSDNの「シリアル化:オブジェクトのシリアル化」
の「CArchive演算子<<および>>の使用法」の中で、演算子<<および>>の右辺にとれるデータ
型が表で紹介されているのですが、この表の一番左上に、「CObject*」とあります。これは
CObject派生クラスのポインタもあてはまるのでしょうか。もしあてはまるのなら、他のクラス
のオブジェクトのポインタを保存するにはどうすればいいのでしょうか。

今、2つのクラスCA,CBがあり、CBクラスのメンバにはCAクラスのオブジェクトのポインタを
もつ予定のm_pAObjectがあります。

Class CA : public CObject

Class CB : public CObject
   CA* m_pAObject

CBクラスのSerialize()関数はつぎのような形です。

void CB::Serialize(CArchive &ar)
{
   if( ar.IsStoring( ) )
   {
      ar << m_pAObject;
   }
   else
   {  
      ar >> m_pAObject;
   }
}

m_pAObjectにポインタが入るのは、CBクラスのコンストラクタ以外の関数内で入れると
します。
これを実際シリアル化してファイルを保存し、再度、このファイルを開いたとしても実際に
作られたオブジェクトはまた新しいものができてしまって、保存したポインタは意味をもた
なくなってしまうと思います。このことを考えると、どうしてMSDNに「CObject*」と書いて
あるのかがわかりません。どういう風に使うのでしょうか。実際に、オブジェクトのポインタ
などは保存しないで、そのクラスのコンストラクタなどで取得するようにしなければいけない
のでしょうか。
 シリアル化についてはまったくわからないことだらけで、見当違いなことをいっていないか
心配です。何か間違えなどがあればご指摘ください。よろしくお願いします。

dairygoods 2002/05/01(水) 10:30:10
> ar >> m_pAObject;
> ar << m_pAObject;

これによって、ポインタが保存/復元されるのではなく、
m_pAObject->Serialize()がよびだされます。
CAクラスでSerialize()をちゃんとオーバーロードしておけば、
CAオブジェクトの中身がCBと一緒にシリアライズされます。
(DECLATE_SERIAL()とIMPLEMENT_SERIAL()も必要)

復元の時はMFCが内部でCAオブジェクトをnewしてから、
Serialize()を呼び出し、そのポインタをm_pAObjectに割当てます。
同じCAオブジェクトのポインタを複数の場所で
保存した場合、復元したときは、それぞれが
同じ内容のコピーを持つことになります。

そのため、この機能は、CBオブジェクトが内部的にCAを管理している
場合にのみ使用すべきだと思います。

はまち 2002/05/02(木) 02:32:28
dairygoodさん、いつもご丁寧なお返事、ありがとうございます。とてもよくわかりました。今
まで漠然としてたところがとてもすっきりわかった感じです。特に、一番引っかかってたのは

>そのため、この機能は、CBオブジェクトが内部的にCAを管理している
>場合にのみ使用すべきだと思います。

の部分です。ドキュメントで宣言した他のクラスへのポインタを、それぞれのクラスが持って
いて、それがどうして保存できないのかと思案に暮れていたので助かりました。もう一息で
だいぶわかりそうなのですが、もしよかったら確認させて下さい。

今、CBクラスが、CAクラスの実体へのポインタを持つ予定であるメンバオブジェクトを3つ
持っています

class CB
   CA* m_pAObject1;
   CA* m_pAObject2;
   CA* m_pAObject3;

ある関数の中でCAクラスの実体が「1つ」作成され、そのポインタを3つそれぞれがもつように
するとします

m_pAObject1 = new CA;
m_pAObject2 = m_pAObject1;  //1と同じポインタをもち、そのポインタのさす実体も同じ
m_pAObject3 = m_pAObject1;  //1と同じポインタをもち、そのポインタのさす実体も同じ

以下のようにシリアル化したとします
if(ar.IsStoring())
{
   ar << m_pAObject1;  //CAのSerializeが呼び出され1の実体のデータを保存
   ar << m_pAObject2;  //CAのSerializeが呼び出され1の実体のデータを保存
   ar << m_pAObject3;  //CAのSerializeが呼び出され1の実体のデータを保存
}
else
{
   ar >> m_pAObject1;  //前の1と同じデータをもつ実体を作成
   ar >> m_pAObject2;  //今の1とポインタ値は違うが前の1と同じデータをもつ実体を作成
   ar >> m_pAObject3;  //今の1とポインタ値は違うが前の1と同じデータをもつ実体を作成
}

このようにシリアル化した場合では、保存する前では、1つだった実体が「3つ」に増えて
いるのではと思います。また、同じ実体をさしていたポインタが、復元後ではさしているデータ
の内容は同じだけど、違う実体をさしているのではないかとも思います。
以上の考え方とdairygoodsさんのいっていたことは同じでしょうか。

すこし長くなってしまったと思います。お仕事に支障ないようで、もしよろしければ、よろしく
お願いします。

dairygoods 2002/05/02(木) 09:38:59
ちょっと気になったので調べてみました。

> 同じCAオブジェクトのポインタを複数の場所で
> 保存した場合、復元したときは、それぞれが
> 同じ内容のコピーを持つことになります。

というのは間違いでした。

CArchiveは、内部に保存するオブジェクトの一覧を保持しています。
そして、同じオブジェクトを指すポインタを保存/復元した場合は、
ちゃんと同じオブジェクトを指すように復元されます。

CArchive::ReadObject, WriteObjectあたりが処理しています。

こんなかんじ。

if(ar.IsStoring())
{
   ar << m_pAObject1;  //初めて保存するポインタなので、内部配列に追加する。
                       //CAのSerializeが呼び出され1の実体のデータを保存。
                       //ついでに、配列のインデクスも保存する。
   ar << m_pAObject2;  //内部配列に同じオブジェクトがあるので、
                       //配列のインデクスだけ保存する。
   ar << m_pAObject3;  //内部配列に同じオブジェクトがあるので、
                       //配列のインデクスだけ保存する。
}
else
{
   ar >> m_pAObject1;  //前の1と同じデータをもつ実体を作成
                       //ポインタを内部配列に保持する。
   ar >> m_pAObject2;  //内部配列に同じオブジェクトがあるので、
                       //m_pAObject1と同じポインタが割当てられる。
   ar >> m_pAObject3;  //内部配列に同じオブジェクトがあるので、
                       //m_pAObject1と同じポインタが割当てられる。
}

ファイルの中のイメージは、
  [0番][CAオブジェクトの中身][0番][0番]
のようになります。


しかし、2つの異なるCArchiveから読み込んだ場合は、
同じ内容の別のオブジェクトになります。

CArchive ar1, ar2;
ar1 >> m_pAObject1;
ar2 >> m_pAObject2;  // m_pAObject1とは別のオブジェクト

はまち 2002/05/03(金) 03:34:00
dairygoodsさん、お返事ありがとうございます。調べていただいて申し訳ないです。
やっぱり、ポインタは保存・復元した後でも目的のオブジェクトを指すのですね。MSDNを
読んでいるとそんな感じもしていたのですが、でも、それだと、どうしても納得いかない点が
あります。実は、今、作業中のプログラムにシリアル化の機能をつけようとしているところ
なのですが、どうしても、あるポインタに復元後に、別の実体をさしてしまっているようで
うまくシリアル化できないところがあります。実体もポインタも、「開く」や「新規作成」から
呼び出されるSerializeのarを渡してシリアル化しているはずなので、問題はないような気が
するのですが。
 実は、もっときちんと返事を書こうと思っていたところのなのですが、少し遅れそうな感じ
がしたので、先に一言、書込みをしておこうと思い今回、書込みしました。できるだけ早く
書こうと思っていますのでもうしばらくお待ちください。

はまち 2002/05/10(金) 06:34:43
[[解決]]
先日はありがとうございました。ポインタのシリアル化の件ですが、うまく実行することが
できるようになりました。なぜ、直ったのかはまだよくわからないのですが、どうやら、

ar << m_pObject
ar >> m_pObject

と

m_pObject.Serialize(ar);

の使い方に問題があったようです。MSDNにも書いてありますが、Serialaize()は

(1)オブジェクトを逆シリアル化するとき、オブジェクトのクラスがわかっているとき
(2)オブジェクトを逆シリアル化するとき、すでにオブジェクトに対するメモリが確保
されてるとき

に使うとありました。どうやら、この(2)の方を間違えていたみたいです。

 まだ完全にシリアル化が成功したわけでないので、原因が特定できませんが、とりあえず
ここでは解決しておこうと思います。もう一息CArchiveについて調べてみようと思いますが、
dairygoodsさん、改めてありがとうございました。

古いスレッドには追加発言できません。