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) |

最近やる気がまったくでないわけで、それが暑さからくるかと言われたらNOで。
なにもまったく手につかないって由々しいので気分転換に映画「INCEPTION」見に行ったりね。

これ、なかなか面白かった。内容も意味もある意味「深い」上、やってることが
プログラムでいう再帰関数だなぁとか思いながら見てたよ。
各所で言われているように、見終わった後誰かと内容について議論できるような、
多少小難しいけれど濃いめなシナリオ。
でもなんというか、そういう話し相手になる人と見に行かないと意味ないんだよね・・・orz


UOからしばし離れてやる気充填のため、Cottonのソースなんかをいじくり。
今現在使っているVBScriptから借用したIRegExpでベンチをとってみると・・・遅い。
すんごい遅い。マクロの処理が重いのはこのせいかぁ。
C++で正規表現、しかもUnicodeで扱うとなると選択肢はかなり限られるわけで、
他の正規表現ライブラリを使おうと鬼車ライブラリとにらめっこしたり。
イマイチ好きになれないんだよね、外部ライブラリに頼るの。
なにかいいものないかなと探してたところ、ありましたよ。
UnicodeもOKで将来性もあって高速なのが。

std::tr1::regex君。

テンプレートで文字列をコンテナとして扱えるので臨機応変だし
C++0xの一部として盛り込まれてるので安泰だ。
Ollyさんで見る限り特殊なランタイムもいらないし、最高。
とりあえず自分用メモでも張って満足感に浸ろう。
#include <tchar.h>
#include <iostream>
#include <string>
#include <regex>
using namespace std;
using namespace std::tr1;
#ifdef _UNICODE
#define _tcout wcout
#else
#define _tcout cout
#endif

int _tmain(int argc, _TCHAR* argv[])
{
// 正規表現のコンパイルとマッチ結果用変数
basic_regex<TCHAR> rx(_T("abc(.+)abc"));
match_results<const TCHAR*> mr;

// 完全一致か確認 regex_matchをregex_searchに置き換えると部分検索になる(1ヒットのみ)
bool bMatched = regex_match(_T("abcdefabc"), mr, rx);
if (bMatched)
{
_tcout << _T("regex_match: matched_string=") << mr.str(0);
_tcout << _T(" $1=") << mr.str(1);
_tcout << _T(" $1.begin=") << mr.position(1);
_tcout << _T(" $1.end=") << mr.position(1) + mr.length(1) << endl;
}

// 置換
basic_string<TCHAR> strText, strFmt, strResult;
strText = _T("abcdefabc abcghiabc");
strFmt = _T("_$1_");
strResult = regex_replace(strText, rx, strFmt);
_tcout << _T("regex_replace: replaced_string=") << strResult << endl;

// すべての一致結果列挙
regex_iterator<basic_string<TCHAR>::const_iterator> rxi(strText.begin(), strText.end(), rx);
regex_iterator<basic_string<TCHAR>::const_iterator> rxi_end;
for(; rxi != rxi_end; ++rxi)
{
_tcout << _T("regex_iterator: matched_string=") << rxi->str(0);
_tcout << _T(" $1=") << rxi->str(1);
_tcout << _T(" $1.begin=") << rxi->position(1);
_tcout << _T(" $1.end=") << rxi->position(1) + rxi->length(1) << endl;
}

return 0;
}

2010.08.05 16:50 | プログラム | トラックバック(-) | コメント(2) |