多数のクラスの中から必要なクラスだけインスタンス化し管理する方法

[戻る]
2005/03/09(水) 22:48:24
こんにちは
いつもお世話になっております

環境
WinXP、VC++6.0、MFC、SDI

質問しておきながら、かなり無理っぽい予感がするのですが・・・
無理としても、代わりになる案か何かがあれば教えていただきたく思い投稿しました

まずこの例を見てください
あくまでもこのようなことをしたい、という例なのでおかしな部分があると思いますが
気にしないで見てください

//全てのクラスの基本となるクラス
class CBase{...};

//CBaseを派生元とするクラス群
class ClassA : public CBase{...};
class ClassB : public CBase{...};
・・・(略)・・・
class ClassY : public CBase{...};
class ClassZ : public CBase{...};

というように同じ基本クラスから派生して作った多数のクラスがあるとします
これを・・・

typedef CTypedPtrArray<CPtrArray, CBase*> ClassPtrArray;

void CreateClassArray(int nFlag, ClassPtrArray& rArray)
{
   [なんらかの型]   ClassName[3][5] = {
      {ClassA, ClassC, ClassH, ClassU, ClassW},
      {ClassB, ClassI, ClassJ, ClassM, ClassQ},
      {ClassE, ClassF, ClassK, ClassO, ClassZ},
   };

   int      nCnt;
   CBase*   pBase;

   for(nCnt = 0; nCnt < 5; nCnt++)
   {
      pBase   = (CBase*)new ClassName[nFlag][nCnt];
      rArray.Add(pBase);
   }
}

のように状況に応じて領域を確保し、ポインタを配列に保存しておきたいのです
上の例では一度にインスタンスを作成するクラスの種類は5つに固定されていますが、
実際は可変になります(nFlagが0のときは5種類、1のときは8種類などがあり得る)

ちなみに今使っている方法は、
全てのクラスのインスタンスをあらかじめ作成しておき、
それらの全てのポインタを配列に入れて保存
そこからインデックスを使って必要なポインタを取り出すというものです

ClassA   classA;
ClassB   classB;
・・・(略)・・・
ClassY   classY;
ClassZ   classZ;

CBase* ClassPtrTable[] = {
   &classA,
   &classB,
   (略)
   &classY,
   &classZ,
}

しかしながら、クラスの数だけでも数百に登り、かなりメモリに無駄が生じます

そこで上に書いたようなことができないものかと思って質問したのですが、
実現する方法はないでしょうか?

必ずしも例のような形でなければならないということはありません
最終的な目標としては

「状況に応じてそのとき使うクラスのインスタンスだけ作成し、
 そのポインタを配列で管理する」

ということが達成できればいいのです

何かよい方法を知っている方がいましたら教えていただけるとありがたいです
よろしくお願いします

しっぽ 2005/03/09(水) 23:59:05
「CBaseを派生元とするクラス群」を
class ClassA : public CBase
{
public:
    static CBase* Create(){return new ClassA;};
};
とし、[なんらかの型]の部分を関数ポインタの配列にすればできると思います。

へも 2005/03/10(木) 00:00:18
インスタンスを作る関数をクラス分用意して
その関数のテーブルを作るとか。

CBase* createA() { return new ClassA; }
CBase* createB() { return new ClassB; }
  :
  :

typedef CBase* (*CreateFunc)();
CreateFunc table[3][5] = {
{createA, createB, createC, createD, createE},
{createF, createG, NULL, NULL, NULL},
  :
};

for(nCnt = 0; nCnt < 5 && table[nFlag][nCnt] != NULL; nCnt++)
{
  rArray.Add(table[nFlag][nCnt]());
}

REE 2005/03/10(木) 09:30:36
関数をあまり増やしたくなければ・・・

enum class_ID { ClassA_ID, ClassB_ID, ClassC_ID, ... };

CBase* CreateClass(class_ID id)  // CBaseのstaticメンバでもよい
{
   switch(id)
   {
      case ClassA_ID: return (CBase*)new ClassA;
      case ClassB_ID: return (CBase*)new ClassB;
      ...
   }
   return NULL; // 又は例外など
}

void CreateClassArray(int nFlag, ClassPtrArray& rArray)
{
   class_ID ClassName[3][5] = {
      {ClassA_ID, ClassC_ID, ClassH_ID, ClassU_ID, ClassW_ID},
      {ClassB_ID, ClassI_ID, ClassJ_ID, ClassM_ID, ClassQ_ID},
      {ClassE_ID, ClassF_ID, ClassK_ID, ClassO_ID, ClassZ_ID},
   };

   int      nCnt;
   CBase*   pBase;

   for(nCnt = 0; nCnt < 5; nCnt++)
   {
      pBase   = CreateClass(ClassName[nFlag][nCnt]);
      rArray.Add(pBase);
   }
}

2005/03/10(木) 10:46:13
[[解決]]
しっぽさん、へもさん、REEさん、レスありがとうございます

なるほど、このような方法があるのですね・・・
確かにこれら方法なら今考えている理想的な処理を実現できます
これからまたこれらの手法を取り込んでクラス構造を練り直します

そしてあとはその数百あるクラスを作成するだけ・・・げんなりですね

しかし、これで行き詰っていたところから先に進めます
どうもありがとうございました

あた 2005/03/10(木) 12:55:03
解決になってますが
MSDN で 動的生成 を調べてみてはどうですか。

CObject からの派生クラスにできるなら
DECLARE_DYNCREATE マクロを埋め込んでおけば
ランタイム クラス情報から動的にオブジェクトを作成できると思います。

MFC の DocTemprate のような管理ができるんじゃないでしょうか。
クラスの型情報も取得できるのでいいと思います

2005/03/10(木) 18:31:40
あたさん、解決チェック済みにも関わらずレスありがとうございます

今までAppWizardが作成するままに使っていたDECLARE_DYNCREATEなどのマクロですが
実際の中身を調べてみてなるほど・・・と、それなりに理解が深まった気がします

確かに今回の目的を達成するためにこの機能を使うのは正解かもしれません
ただMFCでしか使えなくなるという欠点はありますが・・・まぁ他に流用する予定も
特にありませんし

そこでCObjectの動的生成に関して調べ、簡単なテストプログラムを作ってみました
しかし、なにぶんMFCは経験が浅く、
このような書き方で正しいのかいまいち自分で判断できません

下にテストプログラムを載せますので、お手数ですがどなたか
おかしいところがないかどうか簡単でいいのでチェックしていただけないでしょうか?

//■全ての基礎となるクラス
class CBase : public CObject
{
   DECLARE_DYNAMIC(CBase);
public:
   CBase(){}
   virtual ~CBase(){}
   virtual void Test(CString& str) = 0;
};

//■ClassA
class ClassA : public CBase
{
   DECLARE_DYNCREATE(ClassA);
public:
   virtual void Test(CString& str){str += "[A]";}
};

//■ClassB
class ClassB : public CBase
{
   DECLARE_DYNCREATE(ClassB);
public:
   virtual void Test(CString& str){str += "[B]";}
};

//■ClassC
class ClassC : public CBase
{
   DECLARE_DYNCREATE(ClassC);
public:
   virtual void Test(CString& str){str += "[C]";}
};

//■使用するクラスを動的に作成する関数
void CreateClassArray(int nType)
{
   CRuntimeClass* ClassTable[3][3]    = {
      {RUNTIME_CLASS(ClassA), RUNTIME_CLASS(ClassB), RUNTIME_CLASS(ClassC)},
      {RUNTIME_CLASS(ClassC), RUNTIME_CLASS(ClassB), RUNTIME_CLASS(ClassA)},
      {RUNTIME_CLASS(ClassA), RUNTIME_CLASS(ClassC), RUNTIME_CLASS(ClassB)},
   };

   CTypedPtrArray<CPtrArray, CBase*>    PtrArray;
   int          nCnt;
   CBase*   pBase;
   CString   str;

   //テーブルからクラスのインスタンスを作成
   for(nCnt = 0; nCnt < 3; nCnt++)
   {
      pBase   = (CBase*)ClassTable[nType][nCnt]->CreateObject();
      PtrArray.Add(pBase);
   }

   //正しく作成されているかテスト
   for(nCnt = 0; nCnt < 3; nCnt++)
   {
      PtrArray[nCnt]->Test(str);
   }
   AfxMessageBox(str);

   //開放
   for(nCnt = 0; nCnt < PtrArray.GetSize(); nCnt++)
   {
      delete PtrArray[nCnt];
   }
}

これを

CreateClassArray(0);
CreateClassArray(1);
CreateClassArray(2);

と呼び出すと、メッセージボックスがそれぞれ1回ずつ、計3回出て、

1回目:[A][B][C]
2回目:[C][B][A]
3回目:[A][C][B]

と予想通りの結果が出ました

見た目上は期待通りの動作をしています
この書き方で問題がなければいいのですが・・・どうでしょうか?

何かおかしい点等ありましたら指摘していただけるとありがたいです
よろしくお願いします

2005/03/10(木) 18:34:07
すみません、訂正です

//■使用するクラスを動的に作成する関数
void CreateClassArray(int nType)
{
   ・・・略・・・
}

の上あたりに

IMPLEMENT_DYNAMIC(CBase, CObject)

IMPLEMENT_DYNCREATE(ClassA, CBase)
IMPLEMENT_DYNCREATE(ClassB, CBase)
IMPLEMENT_DYNCREATE(ClassC, CBase)

が入っているものと思ってください
書き忘れすみませんでした

あた 2005/03/11(金) 00:41:32
オブジェクトの作成方法は問題ないと思います。


DECLARE_DYNCREATE() マクロは
クラスに下記を追加します

static const CRuntimeClass  classClassName;
static CObject* __stdcall CreateObject() 
{
    return new ClassName;
}


このとき CRuntimeClass のメンバー
m_pfnCreateObject に CreateObject() 関数ポインターを保存します。


RUNTIME_CLASS() マクロは、
クラスの  ClassName::classClassName  staticメンバーのポインターを取り出します。


(&ClassName::classClassName)->CreateObject() この関数の呼び出しで
m_pfnCreateObject の関数を実行します。


動作の仕組みは、しっぽさんの方法です。

2005/03/11(金) 09:45:49
あたさん、お返事と、さらに細かい解説までありがとうございます

解説していただいた内容とMFCのソースを見比べて、
CRuntimeClassの動きをおおよそ理解することができました

おかげさまで作業が進みそうです

最後までお付き合いいただきありがとうございました

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