C++のプログラミングで比較的大きなプロジェクトを扱ったときそれは突然発症し、
それこそガンのようにしつこく手を打つにも難しい問題がでてくる。
その一つが相互参照、あるいは循環参照。
今まさに直面していたり(´・ω・`)

よくあるサンプルで示すと
// ----- A.h -----
#include "B.h"
class A
{
public:
B b;
};

// ----- B.h -----
#include "A.h"
class B
{
public:
A a;
}

クラスAのメンバはBのインスタンスを持ち、かつクラスBはAのインスタンスを持つ。
こうなってはC++ではコンパイルできない・・・
そしてこれまたよくある解決法を示すと
// ----- A.h -----
class B; // 前方宣言(A::bのため)
class A
{
public:
B* b;
};

// ----- B.h -----
class A; // 前方宣言(B::aのため)
class B
{
public:
A* a;
}

includeをやめて前方宣言にし、ポインタを持たせるとすんなり通る。
cppでそれぞれのヘッダをincludeしてやり、ポインタをセットするメンバ関数を作れば問題なし。

・・・なのだが、僕の場合はもうすこし複雑な事情があって問題だらけに。
普通ならC++プログラムはソース(.cpp)とヘッダ(.h)で構成されているのだけど、
ヘッダひとつで宣言と実装を済ませて1クラス1ファイルにしているため、
ポインタからメンバを呼び出そうものなら上記のような解決法ではムリなのだ・・・
(だったら宣言と実装を分けろよって言われたらそれまでなんだけど)

本来、相互参照や循環参照はプログラムの設計そのものが間違っているからこそ
起きることが多いんだけど、世の中必要な相互参照もあるっちゃあるわけで。
機能別にカプセル化しようにも、条件に左右されやすい機能では条件の参照が必要に。
たとえば、BをAに委譲したとき。
(DoIt関数を使った自己委譲は無いものとする)
// ----- A.h -----
#include "B.h"
class A
{
public:
B b;
unsigned long m_uFlags;

A() { b.SetA(this); };
int DoIt() { return b.DoIt(); };
unsigned long GetState() { return m_nFlags; };
};

// ----- B.h -----
class A; // 前方宣言
class B
{
public:
A* pa;

void SetA(A* p) { pa = p; };
int DoIt() { return (pa->GetState() != 0) ? 100 : 50; };
};

例なので荒くざっくり書いたけど、結局エラーがでてコンパイルならず。
じゃぁどうしたらいいんだろう。
卑怯(?)なコードだとこうかな?
// ----- A.h -----
#include "B.h"
class A
{
public:
B<A> b;
unsigned long m_uFlags;

A() { b.SetA(this); };
int DoIt() { return b.DoIt(); };
unsigned long GetState() { return m_nFlags; };
};

// ----- B.h -----
template<class T>
class B
{
public:
T* pa;

void SetA(T* p) { pa = p; };
int DoIt() { return (pa->GetState() != 0) ? 100 : 50; };
};

ポインタを扱わなきゃいけない上にテンプレートorz
あああああああ まどろっこしい!!まどろっこC!!!!!
どのみち委譲なら継承しちゃってもかまわないよね!よね!!!!?

// ----- A.h -----
#include "B.h"
class A : public B<A>
{
public:
unsigned long m_uFlags;

unsigned long GetState() { return m_nFlags; };
};

// ----- B.h -----
template<class T>
class B
{
public:
int DoIt() { return (dynamic_cast<T*>(this)->GetState() != 0) ? 100 : 50; };
};

スッキリ。A.DoIt()も可能だし。
ただ、宣言と実装をまとめたいだけのためにテンプレートまで引っ張り出す必要ってあるんだろうか?
本当はAのデータをクラスCとでもして、A、Bから参照させたほうがベストなんだろうけど。
宣言と実装を分けるべきか、継承か委譲か、設計をどうするか、非常に悩ましい。

2010.08.16 10:39 | プログラム | トラックバック(-) | コメント(1) |

私もこの問題にはいつも悩まされる&いらいらさせられます。
言語の仕様に設計が制限される、時間を取られるのは絶対に間違ってます。
いいかげんC++の仕様を拡張してJAVAみたいにどこに書いた宣言でも使えるようにしてくれないかといつも思います。
過去のコードが動かなくなるから出来ないんだろうけど、コンパイルオプションで切り替えるとかじゃダメなんでしょうかね。

2010.09.25 19:00 URL | 通りすがり #- [ 編集 ]










※投稿されるコメントは管理者の承認後表示されます