クラステンプレートから派生したクラスを、まとめて扱いたいときに、以下のようにす
ることが多いかと思います。
(コンストラクタなどは割愛しています)
template<class X>
class prim
{
protected:
X* m_pdata;
public:
X* Get(){ return m_pdata; }
void SetVertNum(int n){ m_pdata->SetVertNum(n); }
//(b*) void Render(){ m_pdata->Send();}
};
class CLine
{
public:
virtual void Render() = 0;
};
static void _send(int , size_t, void *)
{
;//仮です
}
struct VTX_2D
{
int vert_num;
int pos[2];
void SetVertNum(int n){ vert_num = n*2;}
void Send(){ _send(vert_num, sizeof(int), (void*)pos); }
};
struct VTX_3D
{
int vert_num;
float pos[4];
void SetVertNum(int n){ vert_num = n*4;}
void Send(){ _send(vert_num, sizeof(float), (void*)pos); }
};
class CLine2D : public prim<VTX_2D>, public CLine
{
public:
virtual void Render(){m_pdata->Send();} // (a*)
};
class CLine3D : public prim<VTX_3D>, public CLine
{
public:
virtual void Render(){m_pdata->Send();} // (a*)
};
int _tmain(int argc, _TCHAR* argv[])
{
std::vector<CLine *> vec;
CLine2D *p1 = new CLine2D;
vec.push_back(p1);
CLine3D *p2 = new CLine3D;
vec.push_back(p2);
while(1){
std::vector<CLine*>::iterator it = vec.begin();
for( ; it!=vec.end(); it++)
{
(*it)->Render();
}
}
}
しかし、Render()のように、まったく同じで冗長な感じがする場合があります。
これは仕方のないことなんでしょうか?
(a*)を
CLine2D::Render(){ prim<VTX_2D>::Render(); } // (b*)
CLine3D::Render(){ prim<VTX_3D>::Render(); } // (b*)
とする方法もあり?でも、なんだかこれもすっきりしない・・・と思うのは私だけで
しょうか?
この場合、どのように記述するものなんでしょうか?
う~んと。あんまり回答になってないかもしれませんが、
設計ポリシーの問題かな・・という気分です。
まず、自分ならコンポジット配列に2Dと3Dを混載するようなこと
多分しません。なぜなら文字通り「次元が違う」からです。
従ってプリミティブも最初から次元によって異なるプリミティブを
用意するだろうと思います。つまり、「点クラス」の定義から
始めるでしょうね。
//-------------
// 論理図形クラステンプレート
//-------------
template< typename T>
class T_FIG{};
template< typename T>
class T_FIG2D:public T_FIG< T>{};
template< typename T>
class T_FIG3D:public T_FIG< T>{};
//-------------
// 2D点クラステンプレート
//-------------
template< typename T>
class T_POINT_2D // 2D 点
: public T_FIG2D< T>
{ T x; T y; };
んで、線分(又は論理線)クラスもテンプレート化するかも
//-------------
// 2D線分クラステンプレート
//-------------
template< typename T >
class CLine_2D
: public T_FIG2D< T>
{
T_POINT_2D<T> p[2];
};
typedef CLine_2D<long> CLine_2DL;
typedef std::vector<T_FIG3D< float> *> FIG_ARY_3DF;
このポリシーだと「線分(CLine)の一覧」などという概念は無くなって
「整数2D線分(CLine_2DL)のリスト」または「float3D図形(FIG_ARY_3DF)の
コンポジットリスト」等になると思います。Render()は2D/3D論理図形の
virtualなメンバで、多分何にもしないことになるでしょう。
従ってオーバーライドされたRender()は十分異なる「意味」を
持つ実装になると思います。
う~む、やっぱ回答になってませんね orz。
仲澤@失業者さん、コメントありがとうございます。
とにかく、データの型が違うだけで、必要な処理はほとんど一緒・・・
ならばテンプレートだ!
ということで、テンプレートを使い始めたのですが、まだ設計としては甘いとは思いま
す;
もとがCのものをC++に書き直すついでみたいにやっているので・・・
「同じものはとにかくまとめちゃえ」の発想しかしていなかったので、ご提示いただい
た方針もあるのだと勉強になりました。
>「線分(CLine)の一覧」などという概念
これにこだわるのも良くないのかもしれませんね。
ありがとうございました。
他の方も、ご意見ありましたらよろしくお願いします。
コメントひとつなく、設計者の意図が伝わってこない。
なのでご意見もヘッタクレもありません。
クラス化の概念に関しては私にも意見があります。
あくまで私の意見であり、一般論として正しいかどうかは未知数。
> 「同じものはとにかくまとめちゃえ」の発想しかしていなかったので、ご提示い
> ただいた方針もあるのだと勉強になりました。
私も以前はそう思っていました。
ただ、これだと関数的な考え方に走りがちなんですね。
似たような関数をまとめるのは「サブルーチン」の考え方であり、C++ではなく、
C言語的な考えになるんですね。
だから、クラスの場合は、
「パーツ化(パッケージ化)」
とみなしています。
その考えに基づくと、
2Dと3Dの違いはポリゴン図形(扱ってるデータはこれだよね?)を構成する点を
2次元座標(X, Y)で表現するか3次元座標(X, Y, Z)で表現するかの違いであり、
本質的にパーツが違うと思います。
ただし、仮想平面を定義し、その平面上での2D座標(X, Y)に高さ情報(H)を加えて
3D座標(X, Y, H)と考えるなら、3D座標は2D座標の延長上にあります。
3D(X, Y, H)に2D(X, Y)を集約させて、(2D, H)というクラスのメンバ構成に
すれば、共通化が可能かと思います(ただし、仮想平面自体が3D座標で表現され
るなら、問題をややこしくするだけなので無意味)。
具体例としては、建築CADの平面図(高さをもった平面)など。
要するに、言いたいことは仲澤@失業者さんと同じことなのですが、
> まず、自分ならコンポジット配列に2Dと3Dを混載するようなこと
> 多分しません。なぜなら文字通り「次元が違う」からです。
別の観点から話してみました。
επιστημηさん
コメントありがとうございます。
簡略化した(つもり)なのでソースにコメントは必要ないかと思っていました。
クラスの個々の意味とか、2Dとか3Dとかに特に特殊な意図はないのです。。。
もっと一般的な単純なものを示すべきでしたね。
お聞きしたかったことは、
「内部で保持するデータ型が違うだけで、ほとんど操作が同じ数種類のクラス」
を実現するのに、クラステンプレートと派生を使ったのですが、
「派生クラスをまとめて扱いたい」
ときに、
「型が違うだけのデータ(クラス)にアクセスする必要があって、どのクラスでも記述が
一緒になってしまうメソッド」
を、
その「まとめて扱うために作った基本クラスから呼べないとだめ」
というのを実現するのに、みなさんが考える
(1)シンプルだと思う方法
(2)オブジェクト指向的にスマートだと思う方法
(3)自分ならこうするという方法
というのが知りたくて投稿しました。
何度か文章で書いたのですが、どうにもうまくかけなかったので、コードを書いたので
すが、それでもうまくお伝えできなかったようで、すみません。
文章で、うまく伝わっていれば幸いです。
bunさん、コメントありがとうございます。
>> 「同じものはとにかくまとめちゃえ」の発想しかしていなかったので、ご提示い
>> ただいた方針もあるのだと勉強になりました。
>私も以前はそう思っていました。
>ただ、これだと関数的な考え方に走りがちなんですね。
>似たような関数をまとめるのは「サブルーチン」の考え方であり、C++ではなく、
>C言語的な考えになるんですね。
やはり、わたしがまだ完全にオブジェクト指向での設計に慣れていないことが大きそう
ですね;
Cからの移植中なので余計そうなのかもしれません。
今回の例の、2Dと3Dでいえば、確かに「次元の違う」ものなので、いっしょくたに扱お
うとすること自体が無理(元のCでの設計ではそぐわない)というふうにも考えられるわ
けですね。
では、2Dとか3Dとかとは無関係な一般的なデータの場合にはどうでしょうか?
図形がらみとかではなくて、こういうような状況になることはあまりない、というかな
いほうが好ましいのでしょうか?
>「パーツ化(パッケージ化)」
という方向で考えていけば、陥ることのないパターンということになるのかな^^;
どうしても、「まとめる」方向に頭がいってしまうので、まだまだ修練不足ですね。
> では、2Dとか3Dとかとは無関係な一般的なデータの場合にはどうでしょうか?
> 図形がらみとかではなくて、こういうような状況になることはあまりない、というか
な
> いほうが好ましいのでしょうか?
うーん、、、
うまく説明できるか自信がありませんが、
テンプレートで扱う対象というのは、テンプレート側のクラスから見て、格納さ
れるクラスが同様に扱えることが重要だと思うのです。
たとえば、
std::vector<CLine*>
なら、vectorはCLine*というデータの集まりを管理するだけです。
だから、<>の中身はどんなデータでも良いわけです。
<>の中身をただの箱としてしか扱ってないので、どんなデータでも扱える代わり
に、vectorテンプレート側からCLine*のメソッドにアクセスすることは無いわけ
です。
次に、以下のような場合(わかりやすくするためクラス名を日本語にする)を考え
ます。
移動手段<車>
移動手段<電車>
この場合、移動手段テンプレート側から、車や電車のメソッドを呼ぶことがある
としたら、移動手段に関するメソッドしか呼ぶべきではないと思うのです。
車クラスに給油()メソッドがあったとしても、移動手段テンプレート側から呼ぶ
のはおかしいです。
テンプレート側からメソッドを呼ぶなら、格納される側のクラスにそのメソッド
が必須(無ければコンパイルエラー)ですから、電車::給油()メソッドが必須にな
ってしまうからですね。電車は電気で動くので給油なんてあり得ないわけです。
このように考えていくと、
ポリゴン<2D座標>
ポリゴン<3D座標>
の考え方に無理があるのが分かるかと思います。
2Dと3D座標は共通には扱えません。
3D → 2D は写像変換が必要
2D → 3D はデータが足りないので不可能です。
なんとなく、伝わりましたでしょうか?
>では、2Dとか3Dとかとは無関係な一般的なデータの場合にはどうでしょうか?
>図形がらみとかではなくて、こういうような状況になることはあまりない、というかな
>いほうが好ましいのでしょうか?
やはり「当該アプリケーションのポリシーによる」と思いますね。
「スピード違反判定用アプリ」では「人」「自転車」「車」を
同じ基本クラス「抽象的な道路を移動する物」から派生させるかもしれませんが、
「景観パース作成アプリ」では同じものを「抽象的な景観構成物」から
派生させるでしょう。
「乗り物図鑑アプリ」では、「人」は「抽象的利用者」から派生させ、
「自転車」「車」は「抽象的乗り物」から派生させることになるかも
しれません。この場合、「人」と「乗り物」を同一に扱うことはできません。
このように実世界では具体的な事物である「人」「自転車」「車」も、
対象アプリの持つ「世界観」によって捕らえ方が異なると思います。
そもそも prim<猫> と prim<犬> とは継承関係どころか
親子/兄弟関係もなにもないまったく別物なのよね。
template<typename X>
class prim : public super_prim { ... }
ならともかくも。
>そもそも prim<猫> と prim<犬> とは継承関係どころか
>親子/兄弟関係もなにもないまったく別物なのよね。
あぁ、そういえばテンプレートの話でしたネ。
継承のドンづまりがテンプレートで終わってる場合は
むしろ「派生したクラス同士は似ているけど無関係な物」
であることを意識すべきですね。
bunさん、仲澤@失業者 さん、επιστημηさん、コメントありがとうございま
す。
やはり、わたしの捉え方、世界観の作り出し方に問題があるようです。
まだまだC的な考えから抜け出せていないようです。
だいぶオブジェクト指向になってきた!と自負していたのですが、今回、全然だという
ことが分かりました。
仲澤@失業者 さんの、
>対象アプリの持つ「世界観」によって捕らえ方が異なると思います。
今後の指針のヒントになりそうです、ありがとうございました。
蛇足になってしまうかもしれませんが・・・
επιστημηさん、
猫と犬を「4足歩行動物」として扱う場合は、
template<typename X>
class prim : public 4足歩行動物{...}
としたうえで、prim<猫> と prim<犬>とすべき、ということですね。
今までのわたしは、
template<typename X>
class 4足歩行動物{}
class 猫 : public 4足歩行動物<猫型>{}
class 犬 : public 4足歩行動物<犬型>{}
としていたというわけですね。
そうか・・・なるほど・・・
というか、普通にクラス化を考えるときは上記のようになるのが普通ですよね;
内部型だけ違うからテンプレートだ!と思い込んだあたりから、どうやら間違った方向
にいっているようです。
もう一度ちゃんと、関係を見直してみます。
重ねて、皆さんありがとうございました。
仲澤@失業者 さん
時間差でしたね;
>継承のドンづまりがテンプレートで終わってる場合は
>むしろ「派生したクラス同士は似ているけど無関係な物」
>であることを意識すべきですね。
逆に、「関係性がある」場合には、この設計ではだめということですね。
よく分かりました^^;
もやもやしていたものがすっきりしました。
ありがとうございました!
一応<解決>押しておきます。
皆さんありがとうございました。
> template<typename X>
> class prim : public 4足歩行動物{...}
> としたうえで、prim<猫> と prim<犬>とすべき、ということですね。
うん、そーしておけば
virtual void 4足歩行動物::歩く() を
prim<犬>::歩く() や prim<猫>::歩く() で再定義し、
4足歩行動物* 未確認歩行物体 = new prim<なにか>();
未確認歩行物体->歩く(); できますな。