[C++] C++ で LINQ を実装してみるテスト(前哨戦)

ん~、前哨戦で終了するかもしれませんが、自分の勉強がてら。と、今使っているシステムにできれば組み入れたいので。
将来的に LINQ for C++ ができたとしても、VC++2010 に組み入れられるとは限らないので、やむなく自作ってところでしょうか。実験的なものでなので、拡張性/可読性を考えて導入するかどうかは決めましょう、ということで。

ラムダ式と std::function が使えるようになったから、じゃあ、LINQ らしいメソッドチェーンの方法を試してみたらと思ってざっと書いてみると。

GoingNative 9: LINQ for C/C++, Native Rx, Meet Aaron Lahman | C9::GoingNative | Channel 9
http://channel9.msdn.com/Shows/C9-GoingNative/GoingNative-9-LINQ-for-C-Native-Rx-RxC-Meet-Aaron-Lahman

ああ、確かに上記と同じ方法になります。

#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <tuple>
using namespace std;

class Vector : public vector<int> {
protected:
	Vector *_v;
public:
	Vector() :
		_v(NULL) {
	}
	~Vector() {
		if ( _v != NULL ) {
			delete _v;
		}
	}
	Vector &From( vector<int> &vec ) {
		// アップキャストしてるが、今回はこれで ^^;
		return *(Vector*)&vec;
	}
	Vector &Where(function<bool(int n)> func) {
		// 毎回 new しているが、遅延実行させるため iterator を使う予定
		if ( _v != NULL ) delete _v;
		_v = new Vector;
		for_each( begin(), end(),
			[=](int n) { if(func(n)) _v->push_back(n); });
		return *_v;
	}
	Vector &Select(function<int(int n)> func) {
		if ( _v != NULL ) delete _v;
		_v = new Vector;
		for_each( begin(), end(),
			[=](int n) { _v->push_back(func(n)); });
		return *_v;
	}
};


int main( void )
{
	Vector vec;
	for ( int i=0; i<10; i++ ) {
		vec.push_back( i );
	}
	
	auto query =
		vec.Where([](int &n)->bool{ return n % 2 == 0 ; })
		.Select([](int &n)->int{ return n*10 ; });
		
	for_each( query.begin(), query.end(),
		[](int n) { cout << n << endl; });
}

肝心の「遅延評価」ができていないので、LINQ とは言えませんが、まあ、表面上はコンパイルが通るし、実行も正常。

D:\work\blog\src\alice>lamda001
0
20
40
60
80

_v = new Vector() してから return *_v で method chain を実現しているんですが、これ memory leak してないか?と思ったのですが、調べるとリークしてませんね。参照で返しているのでブロック外に出ると自動的に解放されています。

元々の vecotr ではなくて、Vector を使わないと駄目なのは、駄目駄目なのでこれはなんとかしないと。
あと遅延評価は C# の IEnumable 相当を作るよりも、iterator をそのまま使いたいんですよね。やっぱり STL と互換性を持たせたいから。

カテゴリー: C++ | [C++] C++ で LINQ を実装してみるテスト(前哨戦) はコメントを受け付けていません

[c++] ラムダ式は std::function で保存せよ

関数ポインタを無理矢理取得して、別の関数ポインタに入れる方法 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3580

なところで、関数ポインタを無理矢理 void * に入れて保存すれば ok ? と妄想していたのですが、いやいや、std::function を使えば lambda 式を保存できるよ、という話です。
要は、for_each や remove_if のような algorithm 系の関数に設定する関数をどうしたら class に押し込めるか?という問題だったので。

■実験用のソース

実験したコードはこんな感じです。

#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

class Alice 
{
protected:
	vector<string> _lst;
public:
	Alice() {
		_lst.push_back(&quot;alice&quot;);
		_lst.push_back(&quot;luwis&quot;);
		_lst.push_back(&quot;lolita&quot;);
	}
	void disp1() {
		// lambda 式を直接扱う
		for_each( _lst.begin(), _lst.end(),
			[](string &s){ cout << &quot;name: &quot; << s << endl; });
	}
	void disp2() {
		// lambda 式を一度 auto 変数に入れる
		auto func = [](string &s){ cout << &quot;name: &quot; << s << endl; };
		for_each( _lst.begin(), _lst.end(), func );
	}
	
	// こんな風にクラスメソッドで扱うにはどうすればよいのか?
	void func(string s) {
		cout << &quot;name: &quot; << s << endl; 
	}
	void disp3() {
		// これはコンパイルエラー
		// for_each( _lst.begin(), _lst.end(), func );
		// これもコンパイルエラー
		// for_each( _lst.begin(), _lst.end(), mem_fun(&Alice::func));
		// 代替案として lambda でくるむ
		for_each( _lst.begin(), _lst.end(),
			[this](string s){ this->func(s); });
	}
	

	// std::function で定義	
	function<void(string)> func1 ;
	void disp4() {
		// コンストラクタ等で定義しておく
		this->func1 = [](string s){ cout << &quot;name: &quot; << s << endl; };
		// クラスの内部関数風に呼び出す
		for_each( _lst.begin(), _lst.end(), func1 );
	}
};

int main( void )
{
	Alice alice;
	
	alice.disp1();
	alice.disp2();
	alice.disp3();
	alice.disp4();
	
	return 0;
}

Alice::disp3 のように、Alice::func を表示用の関数として for_each から呼び出したいわけです。
普通に lambda 式を使うのが良いのですが、それを Alice クラスの内部関数として定義しておきたいわけです。これは、lambda 式の中身が複雑化したり複数の場所で使われるときに、一括管理しておきたいという想定です。lambda 式自体は auto 変数に代入しておくと、そのメソッド内では利用できるのですが、他では再利用できないので、クラス内のメンバ関数としようとしている訳です。

で、通常のメンバ関数の場合は &Alice::func のように取れるのですが、for_each に渡さないいけないのはグローバル関数か、要素のメソッドのなのです。この場合、vector としているので、要素の型である string に関数を仕込むのは無理があります。avoid な方法としては、string を一旦くるむ方法もありますが、かなり冗長です。

そこで出た代案としては、

		// 代替案として lambda でくるむ
		for_each( _lst.begin(), _lst.end(),
			[this](string s){ this->func(s); });

な方法です。非常にダサいですが、一度 lambda 式でくるんでメンバ関数を呼び出します。まあ、ダサいですが実用には耐えられますね。

じゃあ、ひょっとすると関数ポインタをなんらかの形で void * として保存しておいて、利用する時にもう一度元の関数型に直してやれば、動くのではないか?と思ったのが、「関数ポインタを無理矢理取得して~」の方法なのですが、いやいや、そこまでやる必要はなかったということです。

	// std::function で定義	
	function<void(string)> func1 ;
	void disp4() {
		// コンストラクタ等で定義しておく
		this->func1 = [](string s){ cout << &quot;name: &quot; << s << endl; };
		// クラスの内部関数風に呼び出す
		for_each( _lst.begin(), _lst.end(), func1 );
	}

std::function 型としてメンバ変数を定義しておき、その変数に lambda 式を代入しておきます。本来は、ここを本当のメンバ関数にしたいところですが、贅沢は言いません(苦笑)。これで実用に耐えられるので。
コンストラクタかなんらかの initialize 関数を作っておいて、lambda 式を変数に入れます。つまり、これは変数なので、型さえあっていれば、lambda 式を入れ替えることが可能なんですね。こうなると「for_each( _lst.begin(), _lst.end(), func1 );」の部分はまったく変えずに、func1 の中身だけすりかえしてまえば表示が変わるという仕組みができあがります。

これで、ひとまず目的は達成ということで。

カテゴリー: C++ | 2件のコメント

[c++] for_each と mem_fun の関係をメモ書き

c++ で lambda 式を使うと for_each を使うのが楽になる…のですが、一応、以前の書き方をメモ。

#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

static void f1( string s ) {
	cout << s << endl;
}

int main( void )
{
	vector<string> lines;
	lines.push_back(&quot;masuda&quot;);
	lines.push_back(&quot;tomoaki&quot;);
	lines.push_back(&quot;alice&quot;);

	// 普通にforを使う
	for( auto it=lines.begin(); it!=lines.end(); ++it ) {
		cout << (*it) << endl;
	}

	// for_each と lambda を使う
	for_each( lines.begin(), lines.end(),
		[](string s){ cout << s << endl; });

	// for_each と static 関数を使う
	for_each( lines.begin(), lines.end(), f1 );

	// for_each と auto 関数を使う
	auto f2 = [](string s){ cout << s << endl; };
	for_each( lines.begin(), lines.end(), f2 );

	return 0;
}

ノーマルに for 文を使って書くと一時変数が必要になる。auto が無かった時代には「vector::iterator it = lines.begin()」と書かないと駄目だったのですが、このあたりは auto を使えば十分。この長い書き方が嫌で typedef したりするのですが、今は不要です。

	// 普通にforを使う
	for( auto it=lines.begin(); it!=lines.end(); ++it ) {
		cout << (*it) << endl;
	}

for_each と lambda 式を使うとこんな感じ。iterator がそのままラムダ式の引数に入るのでそれを使えばOKです。
このあたりが LINQ っぽく書けるってことなのですが、このままではほど遠いかなと。まあ、慣れかもしれませんが、イテレーターを lambda 式の引数として受けないといけないのがいまいちです。

	// for_each と lambda を使う
	for_each( lines.begin(), lines.end(),
		[](string s){ cout << s << endl; });

lambda 式を auto で受けて関数ポインタとして使う方法です。lambda 式部分が長い場合や、動的に切り替えたい場合に使える…と思うのですがよくわからず。
本来は、この auto を関数の外側に出したいのですが、それを真面目に出すと…

	// for_each と auto 関数を使う
	auto f2 = [](string s){ cout << s << endl; };
	for_each( lines.begin(), lines.end(), f2 );

下記のように、処理関数を外側に出します。これだと普通ですね。というか、これが最初の for_each の使い方です。

static void f1( string s ) {
	cout << s << endl;
}

...
	// for_each と static 関数を使う
	for_each( lines.begin(), lines.end(), f1 );

■クラス内で for_each を使う

先の例では、main 関数内で使っていたのですが、今度はクラスのメソッド内で使ってみます。
使い方は、まあ、main 関数と一緒です。内部的に vector でコレクションを持っていて disp_for メソッドなり、一括で表示させる関数を想定しています。

class Alice
{
public:
	vector<string> lines;

	Alice() {
		lines.push_back(&quot;masuda&quot;);
		lines.push_back(&quot;tomoaki&quot;);
		lines.push_back(&quot;alice&quot;);
	}

	void disp_for()
	{
		// 普通のfor文
		for( auto it=lines.begin(); it!=lines.end(); ++it ) {
			cout << (*it) << endl;
		}
	}
	void disp_for_lambda() {
		// lambda 式を使う
		for_each( lines.begin(), lines.end(),
			[](string s){ cout << s << endl; });
	}
	void disp_for_auto() {
		// auto に代入して使う1
		auto f2 = [](string s){ cout << s << endl; };
		for_each( lines.begin(), lines.end(), f2 );
	}

	void f1(string s)
	{
		cout << s << endl;
	}
	void disp_for_inner_func()
	{
		// 出来そうで、できない???
		// for_each( lines.begin(), lines.end(), &Alice::f1 );
		// 素直に lambda 式から呼び出す
		for_each( lines.begin(), lines.end(),
			[this](string s){ this->f1(s); });
	}
};

main 関数とちょっと違うのは、以下の disp_for_inner_func メソッドのところです。
書き方としては、for_each で Alice クラスの内部関数 f1 を使いたいところなのですが…これは出来ません。
関数ポインタ「&Alice::f1」を使って、lambda 式のようにイテレーターを引数にして、と思って使ってみると、どうやってもコンパイルが通りません。

	void f1(string s)
	{
		cout << s << endl;
	}
	void disp_for_inner_func()
	{
		// 出来そうで、できない???
		for_each( lines.begin(), lines.end(), &Alice::f1 );
		// 素直に lambda 式から呼び出す
		for_each( lines.begin(), lines.end(),
			[this](string s){ this->f1(s); });
	}

VC++ でコンパイルと以下のようなエラーになります。

C:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE\algorithm(22) : error C
2064: 1 引数を取り込む関数には評価されません。
        C:\Program Files\Microsoft Visual Studio 10.0\VC\INCLUDE\algorithm(32) :
 コンパイルされたクラスの テンプレート のインスタンス化 '_Fn1 std::_For_each<std
::basic_string<_Elem,_Traits,_Ax>*,_Fn1>(_InIt,_InIt,_Fn1)' の参照を確認してくだ
さい
        with
        [
            _Fn1=void (__thiscall Alice::* )(std::string),
            _Elem=char,
            _Traits=std::char_traits<char>,
            _Ax=std::allocator<char>,
            _InIt=std::basic_string<char,std::char_traits<char>,std::allocator<c
har>> *
        ]
        alice029.cpp(66) : コンパイルされたクラスの テンプレート のインスタンス
化 '_Fn1 std::for_each<std::_Vector_iterator<_Myvec>,void(__thiscall Alice::* )(
std::string)>(_InIt,_InIt,_Fn1)' の参照を確認してください
        with
        [
            _Fn1=void (__thiscall Alice::* )(std::string),
            _Myvec=std::_Vector_val<std::string,std::allocator<std::string>>,
            _InIt=std::_Vector_iterator<std::_Vector_val<std::string,std::alloca
tor<std::string>>>
        ]

実は、これは for_each の使い方が間違っているので、コンパイルエラーになるのです。

■メンバ関数は mem_func を使う。ただし要素のメンバ関数に限る

for_each で設定するループ関数は、実は「要素」に対するメソッドになります。lambda 式を使うと分かりづらいのですが、先の for_each では、vector の string に対して関数が適用される、という意味になります。
なので、メンバ関数を for_each に指定する場合は、そのメンバ関数を持つ要素クラスをつくらないと駄目なわけですよ。なるほど。

class Point {
public:
	int x, y, z;
public:
	Point(int x, int y, int z ) {
		this->x = x;
		this->y = y;
		this->z = z;
	}
	void disp() {
		cout << x << &quot;,&quot; << y << &quot;,&quot; << z << endl;
	}
};

int main( void )
{
	// mem_fun はこう使う
	vector<Point*> ps;
	ps.push_back( new Point(1,1,1));
	ps.push_back( new Point(2,2,2));
	ps.push_back( new Point(3,3,3));
	for_each( ps.begin(), ps.end(), mem_fun(&Point::disp));
}

こんな風に、Point クラスを作っておいて、disp メソッドがあるという場合を想定します。
vector コレクションに対して、for_each を適用すると、この disp メソッドを適用する、ということになるのです。
で、メソッド関数の場合にはそのまま適用できないので、上記のように mem_fun という補助関数を使います。内部的には、vecotr::iterator に対して disp メソッドを呼び出すという、関数ポインタを使うという作りになっています。

確かに、for_each だけでなく、remove_if や find_if のような比較関数を設定する場合、要素に対して comp 関数がある訳で、納得できる仕様ですよね。

ちなみに、find_if に比較関数 comp を追加して表示させるとこんな風になる。

class Point {
public:
	int x, y, z;
public:
	Point(int x, int y, int z ) {
		this->x = x;
		this->y = y;
		this->z = z;
	}
	void disp() {
		cout << x << &quot;,&quot; << y << &quot;,&quot; << z << endl;
	}
	bool comp( Point *pt ) {
		if ( this->x == pt->x &&
		     this->y == pt->y &&
		     this->z == pt->z ) {
			return true;
		} else {
			return false;
		}
	}
};

int main( void )
{
	Point *p1 = new Point(2,2,2);
	auto it = find_if( ps.begin(), ps.end(),
		bind2nd(mem_fun(&Point::comp),p1));
	if ( it != ps.end() ) {
		cout << &quot;found.&quot; << endl;
	} else {
		cout << &quot;no found.&quot; << endl;
	}
	return 0;
}

比較する Point ポインタを渡すために、bind2nd 関数を使うところが「アレ」ですが、まぁ、できます。
うーん、C++ template パズルを作りたい場合はこれでもいいのですが、実運用として(特に可読性としては)はこれはいまいちかなぁと常々思っています。

この comp という比較関数ですが、Point クラスに属さないと駄目なところが欠点ですよね。vector の場合はよいのですが、さて、vectorとか、vectorの場合はどうすれば良いのか?ってことです。比較関数として equal_to() とか使えばいいんですかね?

	auto b = find_if( lines.begin(), lines.end(),
		bind2nd( equal_to<string>(), &quot;tomoaki&quot; ));
	if ( b != lines.end() ) {
		cout << &quot;found.&quot; << endl;
	} else {
		cout << &quot;no found.&quot; << endl;
	}

まあ、慣れるとそれでもいいのですが、lambda 式があったり LINQ があったりする時代なので、ちょっとパズルは嫌だなぁと。
今だったら、下記のように lambda 式を使います。

	// lambda を使う
	auto b2 = find_if( lines.begin(), lines.end(),
		[](string s)->bool{ return s == &quot;tomoaki&quot;; });
	if ( b2 != lines.end() ) {
		cout << &quot;found.&quot; << endl;
	} else {
		cout << &quot;no found.&quot; << endl;
	}

このほうが可読性が高いです、と私は思います。

■プリミティブなクラスに比較関数をつけるのか?

で、話を戻すと、

	void disp_for_inner_func()
	{
		// 出来そうで、できない???
		// for_each( lines.begin(), lines.end(), &Alice::f1 );
		// 素直に lambda 式から呼び出す
		for_each( lines.begin(), lines.end(),
			[this](string s){ this->f1(s); });
	}

のところで、内部関数を渡せるほうが読み方としては私にはなんとなく自然な訳です。remove_if や find_if には equal_to のような関数が用意されていますが、カスタム表示のような disp 関数の場合は無理ですよね。
逃れる方法としては、ラムダ式からメンバ関数を呼び出すので、これで十分用途は足りるのですがなんかダサい。。。

class Point {
public:
	int x, y, z;
public:
	Point(int x, int y, int z ) {
		this->x = x;
		this->y = y;
		this->z = z;
	}
	void disp() {
		cout << x << &quot;,&quot; << y << &quot;,&quot; << z << endl;
	}
	bool comp( Point *pt ) {
		if ( this->x == pt->x &&
		     this->y == pt->y &&
		     this->z == pt->z ) {
			return true;
		} else {
			return false;
		}
	}
};

あと、発想として Point クラスに比較関数などなどが付け加わるのもいまいちなのです。Point クラスはデータクラスとして必要最低限にとどめておきたいし、のちのちに拡張させないようにしたい。C++ 的には include 先が変更されるのは避けたいわけで、そうなると「将来追加されるかもしれない」comp 関数などを追加しておくのは「到底無理」な訳です。
そうなると、comp 関数のほうも使うときに適宜用意するのが適当で、

Alice {
	vector<Point*> ps;
public:
	bool comp( Point *p1, Point *p2 ) {
		if ( p1->x == p2->x &&
             p1->x == p2->y &&
             p1->x == p2->z ) {
			return true;
		} else {
			return false;
		}
	}
	bool find( Point *p ) {
		return for_each( ps.begin(), ps.end(),
			&Alice::comp, p );
	}
};

な風に、2つの引数を持つ comp メソッドを for_each から直接呼び出せたらよいなぁ、と。comp メソッドの中身はここでは x,y,z の全てを比較していますが、workarea などを含めると独自に comp を作ったほうが良い場合が多いのです。高速化のために内部的には id を比較するだけとか。

あとは、LINQ 風に

bool find( Point *p ) {
	return ps.from( it ).where(it == *p );
}

あるいは

bool find( Point *p ) {
	auto result = ps.from(it).where(it==*p);
	return result != ps.end();
}

としてしまうとか。まあ、こっちのは話はまた別の機会に。

カテゴリー: C++ | 2件のコメント

C++ で ObservableCollection を実装する

ObservableCollection(T) クラス (System.Collections.ObjectModel)
http://msdn.microsoft.com/ja-jp/library/ms668604.aspx
Skeleton of GOF’s Design Pattern
http://www002.upp.so-net.ne.jp/ys_oota/mdp/Observer/index.htm

2つのViewの変更が、同時に行われるときのModelの挙動を考察 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3575

で、__event/__hook で実装しようと思っていたのですが、基本に帰って Observer パターンを調べてみると、ああ、これでいいじゃんということにしました。車輪の再発明ってやつです。

  • View と Data/Model を独立させよう
  • Model の変更を複数の View に伝える

という点も従来の Observer パターンで ok です。
このあたり、notify で渡す時に、notify( Model * ) のように Model の型を渡してしまうと Model の型に縛られる(当たり前)ってことになるのですが、ここは object 型/void *で十分な訳です。受ける側で、(Model*)object としてキャストをすれば良いわけで…なるほど。

複数の view への通知に対しては、addObjserver/revemoOPbserverを実装して通知先の Observable をコレクションしておきます。元々の Observer パターンがこうなので、私の見落としですね。

で、GridView と データコレクションとの連携、いわゆるコレクションのデータバインドに関しては、ObservableCollection が良く使われるので、これを C++ で実装してみます。

// Doc-View に直す暫定 Observable パターン
#include <string>
#include <iostream>
#include <list>
#include <algorithm>

using namespace std;

class IObserver {
public:
	// Action
	enum Action {
		Add,
		Remove,
		Replace,
		Move,
		Reset,
	};
	virtual void CollectionChanged( void *ob, Action mode ) {}
	virtual void PropertyChanged( void *ob, const char *propName ) {}
};

class IObservable {
protected:
	std::list<IObserver*> _lst;
public:
	void addObserver( IObserver *ob ) {
		_lst.push_back( ob );
	}
	void removeObserver( IObserver *ob ) {
		_lst.remove( ob );
	}
	void notify( const char *propName ) {
		for ( auto it = _lst.begin(); it != _lst.end(); ++it ) {
			(*it)->PropertyChanged( this, propName );
		}
	}
};

template <class T> class ObservableCollection : public std::list<T> {
protected:
	std::list<IObserver*> _lst;
public:
	void addObserver( IObserver *ob ) {
		_lst.push_back( ob );
	}
	void removeObserver( IObserver *ob ) {
		_lst.remove( ob );
	}
	void notify( T item, IObserver::Action mode ) {
		for ( auto it = _lst.begin(); it != _lst.end(); ++it ) {
			(*it)->CollectionChanged( item, mode );
		}
	}
public:
	// override
	void push_back( T item ) {
		std::list<T>::push_back( item );
		notify( item, IObserver::Add );
	}
	void remove( T item ) {
		std::list<T>::remove( item );
		notify( item, IObserver::Remove );
	}
	void removeAt( int index ) {
		for ( auto it=begin(); it!=end(); ++it ) {
			if ( index-- == 0 ) {
				remove( *it );
			}
		}
	}
	void insert( std::list<T>::iterator it, T item ) {
		std::list<T>::insert( it, item );
		notify( item, IObserver::Add );
	}
		
	void insertAt( int index, T item ) {
		for ( auto it=begin(); it!=end(); ++it ) {
			if ( index-- == 0 ) {
				insert( it, item );
			}
		}
	}
	void clear() {
		std::list<T>::clear();
		notify( item, IObserver::Reset );
	}
};

#define PROPERTY( _t, _m ) \
public:	_t m_##_m;	\
public:				\
	__declspec(property(get=get##_m,put=set##_m)) _t _m;	\
	_t get##_m() { return m_##_m; }	\
	void set##_m( _t v ) {			\
		if ( m_##_m != v ) {		\
			m_##_m = v;				\
			notify( #_m );			\
		}							\
	}

class Data : public IObservable {
public:
	PROPERTY( string, Name );
	PROPERTY( int, Age );
public:
	Data( string name, int age ) {
		this->m_Name = name;
		this->m_Age = age;
	}
};
class View : public IObserver
{
public:	
	virtual void CollectionChanged( void *sender, IObserver::Action mode ) 
	{
		Data *data = (Data*)sender;
		cout << &quot;name: &quot; << data->Name << &quot; &quot; << mode  << endl;
	}
	virtual void PropertyChanged( void *sender, const char *propName ) 
	{
		Data *data = (Data*)sender;
		cout << &quot;name: &quot; << data->Name << &quot; &quot; << propName  << endl;
	}
};

int main( void )
{
	ObservableCollection<Data*> lst;
	
	lst.push_back( new Data( &quot;masuda&quot;, 20 ));
	lst.push_back( new Data( &quot;tomoaki&quot;, 30 ));
	lst.push_back( new Data( &quot;alice&quot;, 10 ));
	
	View view;
	// append handler
	lst.addObserver( &view );
	for_each( lst.begin(), lst.end(), 
		[&view](Data *v){ v->addObserver( &view );});
	
	Data *d = new Data( &quot;nanashi&quot;, 20 );
	d->addObserver( &view );
	// append item
	lst.push_back(d);
	// change property
	d->Name = &quot;NANASHI&quot;;
	
	return 0;
}

C#/VB の ObservableCollection と合わせるために、CollectionChanged と PropertyChanged というイベントが発生します。通知元の Model/Data では IObservable を継承して、通知先の View では IObserver を継承します。__event/__hook を使うと、直接メンバ関数にマッピングができるので継承自体がいらないのですが、名称の統一性を考えると明示的に継承したほうがよさそうです。

Collection にした場合、それぞれの要素の IObserver のコレクション _lst が無駄に思えるので、もう少し改修は必要でしょうねぇ。まぁ、昨今のメモリ事情ならばこのままでも良いかと。

IObserver/IObservable というインターフェースのような名前にしていますが、実装が入っています。純粋関数にしても良いのですが、継承するけど通知されたくない(?)という中途半端な状態でも ok なので、これでよいかと。

カテゴリー: C++ | C++ で ObservableCollection を実装する はコメントを受け付けていません

関数ポインタを無理矢理取得して、別の関数ポインタに入れる方法

__event/__hook 関数を使う理由のひとつとして、クラスの関数ポインタを別のクラスの関数ポインタには容易に入れられない、というのがあって。

class Alice {
	string _name;
public:
	void (*Say)(string name);	// ここで関数ポインタを定義
};

というようにポインタを定義しておいて、これを設定したいわけですが、これに設定できるのは Alice::func(string) という関数だけなんですね。これが比較関数だったりすると、他のクラスから設定できないし(内部のメソッドでないと駄目だし)という具合で困るわけです。
STLの比較関数(for_each とか remove_if など)の algorithm 系はグローバルの関数なので大丈夫なんですが…

まぁ、そんな訳で __event と __hook の組み合わせにするのが普通(?)なのですが、やっぱり関数ポインタまま使いたい、というのを実装したのが以下です。

// 関数ポインタを無理矢理取得
#include <string>
#include <iostream>
#include <stdarg.h>

using namespace std;

class Alice {
	string _name;
public:
//	__event void SayEvent(string name);
	void (*Say)(string name);
};

class Lewis {
public:
	void Say( string name )
	{
		cout << &quot;in Lewis: &quot; << name << endl;
	}
};

void *funcp( int dummy, ... )
{
	va_list ap;
    va_start(ap, dummy);
    int ret = va_arg(ap, int);
    va_end(ap);
    return (void*)ret;
}
void hook( int dummy, ... )
{
	int *p = &dummy;
	int src  = (int)p[1];
	int *dest = (int*)p[2];
	*dest = src;
}

int main( void )
{

	Alice alice;
	Lewis lewis;

//	__hook( &Alice::SayEvent, &alice, &Lewis::Say, &lewis );
// 	alice.SayEvent( &quot;Alice&quot; );

	void *p = funcp(0,&Lewis::Say);
	void *p2 = funcp(0,&alice.Say);

	printf(&quot;say:   %08X\n&quot;, &Lewis::Say );
	printf(&quot;funcp: %08X\n&quot;, p );
	printf(&quot;say:   %08X\n&quot;, &alice.Say);
	printf(&quot;funcp: %08X\n&quot;, p2 );

	hook(0, &Lewis::Say, &alice.Say );
	alice.Say( &quot;Alice X&quot; );

	return 0;
}

hook 関数がそれですね。試しに使った funcp 関数を観て貰うと分かりますが、可変引数のスタックを使って取り出すという方法です。
これがトリッキーなのは、可変引数の場合は型のチェックがされないことです。C++ の場合、型変換が厳しくて、関数ポインタと void * の相互変換ができません。なので無理矢理ポインタで解決しています。

これで、__event/__hook が消えて、gcc でもコンパイルできそうで、先の model と view の関係にも使えるね…と思っていたのですが、よくよく考え直してみると、model-view の関係には Observable パターンを使えば即解決というのが分かりました orz __hook/__event を使う必要がないってことで「車輪の再発明」。きれいな Observable パターンは後程。それだけじゃつまらないので、C++ で ObservableCollection を実装してみます。

カテゴリー: C++ | 2件のコメント

2つのViewの変更を以前はどう書いていたかを解説

いきなり、MVVM もどきの __event/__hook に持って行くと移行の方法がわからなくなるので、それ以前の話を残しておきます。

■現状は同じ

  • GridView と GraihpcsView の 2つの View がある。
  • GridView の項目をチェックすると、GraphicsView のある点が反転する。
  • 逆に GraphicsView のある点を反転させると、GridView の項目のチェックが反転する。

という、2つのViewの相互伝播の機能を実現するとして、__event/__hook なしでどのように書いてある/書いたのかというと…

■実装の仕方は2つある

実装の仕方は2つあります。オブジェクト指向的に GridView, GraphicsView に適当なインターフェース(OnClick, OnCheck, OnChanged)を View に配置させて、相互に呼び出しをします。

class Model ;
class GraphicsView {
	void OnClick();
	void Onchanged( Model *model );
};
class GridView {
	void OnChecked();
	void OnChanged( Model *model );
};

GraphicsView *graphicsView;
GridView *gridView;

void GraphicsView::OnClick() {
	gridView->OnChanged( model );
}
void GridView::OnCheck() {
	graphicsView->OnChanged( model );
}

とします。一見、これで平和なような気がしますが、これに新たに View を加えると大混乱に陥るのですよ。

class Model ;
class GraphicsView {
	void OnClick();
	void Onchanged( Model *model );
};
class GridView {
	void OnChecked();
	void OnChanged( Model *model );
};
// 新しい View を追加
class OtherView {
	void OnChanged( Model *model );
};

GraphicsView *graphicsView;
GridView *gridView;
OtherView *otherView;

void GraphicsView::OnClick() {
	gridView->OnChanged( model );
	otherView->Onchanged( model );	// add;
}
void GridView::OnCheck() {
	graphicsView->OnChanged( model );
	otherView->Onchanged( model );	// add;
}

こんな風に、View 同士の関係が密着過ぎるので、何か変更があるたびにあちこちの実装に手を入れることなります。
全然「疎結合」ではないですよね。

■疎結合を実現させるために、通知を一元管理する

オブジェクト指向の「隠蔽化」≒「疎結合」を実現させるために、View 同士を密着させずにメッセージ処理をするクラスをワンクッション置きます。
ここでよく使われるのが、MainForm クラスですね。大抵が View の親ウィンドウになるので this->GetParent() で拾ってこれるため、((MainForm*)this->GetParent())->OnChange(…) という呼び出しが可能なのです。

class Model ;
class GraphicsView {
	void OnClick();
	void Onchanged( Model *model );
};
class GridView {
	void OnChecked();
	void OnChanged( Model *model );
};
class MainForm {
	void OnChanged( Model *model );
};

GraphicsView *graphicsView;
GridView *gridView;
MainForm *mainForm;

void GraphicsView::OnClick() {
	mainForm->OnChanged( model );
}
void GridView::OnCheck() {
	mainForm->OnChanged( model );
}

void MainForm::OnChanged( model ) {
	graphicsView->OnChanegd( model );
	gridView->OnChanegd( model );
}

一度、MainForm にメッセージを通知してから、2 つの View に通知をしています。これで、OtherView が追加されても、MainForm::OnChanged の中身を変更するだけで済みます。
が、これには問題があって、MainForm は、View の型を知らないといけないのです。GraphicsView, GridView, OtherView と追加されるたびに、MainForm は各 View の #include をしなければいけません。これが結構面倒だし、View のヘッダを修正するたびに MainForm のコンパイルが走ります。
そうそう、MainForm が肥大化するというのは、考えてみれば VM が肥大化する、Controller が肥大化するのと同じ現象です。

■SendMessage を使って疎結合にする

更に疎結合にするために、window handle, WinProc, SendMessage を使います。
「更に疎結合」という書き方をしましたが、実は、これが当時普通に書いていたやり方です。

class Model ;
class GraphicsView {
	void OnClick();
	void Onchanged( Model *model );
};
class GridView {
	void OnChecked();
	void OnChanged( Model *model );
};
class MainForm {
	void OnChanged( Model *model );
};

CWnd *graphicsView;
CWnd *gridView;
MainForm *mainForm;

void GraphicsView::OnClick() {
	mainForm->OnChanged( model );
}
void GraphicsView::WinProc(int msg, WPARAM wparam, LPARAM lparam )
{
	switch ( msg ) {
	case WM_USER_ON_CHANGE:
		this->OnChanged((Model*)lparam);
		break;
	}
}

void GridView::OnCheck() {
	mainForm->OnChanged( model );
}
void GridView::WinProc(int msg, WPARAM wparam, LPARAM lparam )
{
	switch ( msg ) {
	case WM_USER_ON_CHANGE:
		this->OnChanged((Model*)lparam);
		break;
	}
}

void MainForm::OnChanged( model )
{
	graphicsView->SendMessage( WM_USER_ON_CHANGE, NULL, model );
	gridView->SendMessage( WM_USER_ON_CHANGE, NULL, model );
}

View 自体は window handle(HWND)を持っているので、SendMessage の対象にできます。SendMessage は昔から Windows アプリで良く使うメッセージで、いわゆる Command パターンの祖先みたいなものです。メッセージ ID と 2 つのパラメータを渡すのです。この使い方は、.NET の XXXArge クラスにも継承されています。

これで、無理矢理ではありますが、MainForm は GraphicsView などの View の型から自由になります。
上の例のように、graphicsView を CWnd だけで受けるようにしておけば、GraphicsView のヘッダファイルを include しなくて良くなります。

で、この GraphicsView::WinProc の書き方は定番なので(window アプリのイベント駆動という点で)、C++ でのマクロが用意されています。

テクニカル ノート 6: メッセージ マップ
http://msdn.microsoft.com/ja-jp/library/0812b0wa.aspx

にあるように、ON_MESSAGE を使って簡単に書けるのですよ。便利ですね~ < ホントかよッ!!!

class Model ;
class GraphicsView : CWnd {
	void OnClick();
protected:
    //{{AFX_MSG(GraphicsView)
    afx_msg void OnChanged(WPARAM wparam, LPARAM lparam);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

class GridView {
	void OnChecked();
protected:
    //{{AFX_MSG(GridView)
    afx_msg void OnChanged(WPARAM wparam, LPARAM lparam);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

class MainForm {
protected:
    //{{AFX_MSG(MainForm)
    afx_msg void OnChanged(WPARAM wparam, LPARAM lparam);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

#define WM_USER_ON_CHANGE (WM_USER+1)

CWnd *graphicsView;
CWnd *gridView;
MainForm *mainForm;

BEGIN_MESSAGE_MAP(GraphicsView, CWnd)
    ON_MESSAGE(WM_USER_ON_CHANGE, OnChanged)
END_MESSAGE_MAP()

void GraphicsView::OnClick() {
	mainForm->SendMessage( WM_USER_ON_CHANGE, NULL, model );
}

BEGIN_MESSAGE_MAP(GridView, CWnd)
    ON_MESSAGE(WM_USER_ON_CHANGE, OnChanged)
END_MESSAGE_MAP()

void GridView::OnCheck() {
	mainForm->SendMessage( WM_USER_ON_CHANGE, NULL, model );
}

BEGIN_MESSAGE_MAP(MainForm, CWnd)
    ON_MESSAGE(WM_USER_ON_CHANGE, OnChanged)
END_MESSAGE_MAP()

void MainForm::OnChanged( model )
{
	graphicsView->SendMessage( WM_USER_ON_CHANGE, NULL, model );
	gridView->SendMessage( WM_USER_ON_CHANGE, NULL, model );
}

ほら、ON_MESSAGE を使うことによって、WinProc の switch-case が無くなってプログラムコードがすっきりしましたねッ!!!(これは当時、本当にあった「謳い文句」だった)。

まあ、実際にはボタンのクリックイベント(OnClick)などは、VC++ のクラスウィザードやリソースエディタで自動生成してくれるので、こんな風にカスタムにやらない限りはあまり手を付けない分野なのですが…大変といえば大変ですね。実は、C# のイベントハンドリングは自動生成されたコードで、各コントロールのデリゲートと結び付けられているので、構造は似たようなものです。そのあたり、デザイナが仕切ってくれるので楽なのです。かつ、partial でソースが分割されているので、自動生成されたコードと自前のコードが混在されてなくてややこしくないのです。C++ の場合は(何故か C++/CLI もそうなのですが)、自動生成コードと手打ちのコードの混在が激しくて面倒なんですね。分けてくれるとコーディングが楽なんですが… C++/CX でも分離はされていないので、残念ながら望みは薄いでしょう。

■MFC appliceation は先行きどうするべきか?

先ごろ「MFC is dead」って形で、新しい MFC を開発しないということが決まったようなので(まあ、当然の帰着。限られたリソースは適切に配置するということで)、MFC アプリケーションのデザイナが、何らかの形で「改善」されることは望みが薄いのです…と言いますか、この MFC の構造は VC++5 の時代から続いているものです。途中で、ATL/WTL という形でテンプレートを使った windows アプリの提案もなされていたのですが、立ち消えています。いや、存続はしているのですが、MFC を主に使ってきた私には使いづらくて。COM 関係は ATL で作ると便利なんですけどね。

当然、これからの windows アプリケーションは、.NET が主流になる訳で、C#/VB で書くのが普通になります(現在でも普通そうします)。metro style の場合、C++/CX という選択肢もありますが、DirectX を触らなければ、C#/VB のほうが書きやすいでしょう。それに、C++/CX は、言語自体が COM を使いやすいように「勝手に」拡張されているので、WinRT に即したアプリは作り易いですね。

そういう意味では、MFC/C++ の組み合わせを使う場面は、COBOL 並にレガシーなわけで(最近は、Java の Enterprise もレガシー扱いですが)、レガシーはレガシーなままで使うというのもひとつの方法です。しかし、今回、ソースコードが 1.5GB 以上の C++ コードを改修していると(ステップ数は怖くて勘定できないw)、当時の古風なオブジェクト指向(あるいは誤ったオブジェクト指向)に即してレガシーなコーディングで追加をするよりも、できるならば、C# などで培われたイベントの仕組みや LINQ もどきの実装などを汲み入れた形で MFC アプリケーションに追加していくのが良かろうというのが今の私の考えです。

boost を使って、modarn c++ で書き換えることも考えたのですが、どうもこのコード、テンプレートプログラミングをやっても良く/楽にならなそうなんですよね。どうも window メッセージ回り、View 廻りの取り回しで苦労している感じが大。

■MFC applicaiton は何と組み合わせるのか?

MFC に限らずなのですが、OpenCV や Fortran を使うとインターフェースは C++ 一択になります。OpenCV を C# で包む実装もあるにはあるのですが、内部的に新しい視覚認識のコードを作ろうとすると C++ でのコーディングは外せません。また、Fortran の場合、インターフェースがポインタなので、頑張れば C# の unsafe を使ってできないこともないのですが、作業量的に辛いものがあります。
また、既存のコードが、C言語の構造体を使っているとかポインタを前提にして作られていると、C# only ではお手上げなのです。

そうなると、仲立ちとしての C++/CLI が問われるわけで、このあたりきちんと「レイヤー」を分けて作ると、レガシーな C++, Fortran ライブラリを活用できます。

  • View は適宜、C#/VB で作成する。
  • 内部ロジック、レガシーコードの移行は、C++ のままで行う。
  • つなぎを、C++/CLI で書く。

「つなぎ」という言い方をしてますが、C言語の構造体やC++のクラスをそのままC#に直すのは、パフォーマンス的に問題があります。OpenCV の C# インターフェースも各要素を C# 内で扱おうとするとパフォーマンスが低下するでしょう(実測していませんが…なので後で試してみますか)。しかし、View に近いところや、パラメータの部分は C# で扱う方が便利だし、作業も効率的です。そのあたりも踏まえた上で「レイヤー」を意識して「つなぎ」しないといけません。

あとは、そこそこの画面(View)を、C++ だけでさっくりと作れるならばそれに越したことはないのです。ここで、例に出している GridView、GraphicsView は複雑な View なので内部的には C# にするか、DiretX か OpenGL 部分を再構築するか、の選択肢になるのですが、CButton や CEdit などの設定画面をさっくりと作れるようになる、さっくりとカスタマイズできるようになることが必要かと。まぁ、そのあたりはぼちぼちとひと夏かけて考えていきますか。多分。

カテゴリー: C++ | 2つのViewの変更を以前はどう書いていたかを解説 はコメントを受け付けていません

2つのViewの変更が、同時に行われるときのModelの挙動を考察

具体的に言えば、以下の状況を実現させます。

■目的

  • GridView と GraihpcsView の 2つの View がある。
  • GridView の項目をチェックすると、GraphicsView のある点が反転する。
  • 逆に GraphicsView のある点を反転させると、GridView の項目のチェックが反転する。

という、2つのViewの相互伝播を実装します。

■制限

C# でやるのが良いのでしょうが、手元の課題が C++ なので、C++ で実装します。
いや、C# で書いてから C++ に書き直したほうが早いかな?これは、検討を進めながら。

■まずは Model を作る。

2 つの View は同じ Model を共有しているという前提を作ります。直接 View 同士が繋がっているというスタート地点でもよいのですが、それは昔の話。そのあたりは端折って、Model -> View という分離を念頭に入れます。

class Model 
{
public:
	int gridid;
	float x;
	float y;
	float z;
	bool selected;
};

ひとまず、POJO なデータクラスを作ります。x,y,z は三次元データの位置情報、gridid が識別子で、selected が選択状態ですね。
これをコレクションとして扱うので、

class Models : public vector<Model*>
{
};

な感じにします。今回は挿入/削除を考えないので vector でよいかと。

■GridView を作る

class GridView 
{
public:
	Models *models;
public:
	void print() {
		if ( models == NULL ) return;
		for ( auto it = models->begin(); it != models->end(); ++it ) {
			Model *m = *it;
			cout << m->gridid << &quot;:&quot; << m->selected << endl;
		}
	}
	void onChecked( int row, bool selected ) {
		if ( models == NULL ) return ;
		for ( auto it = models->begin(); it != models->end(); ++it ) {
			if ( row == 0 ) {
				Model *m = *it;
				m->selected = selected;
				break;
			}
			row --;
		}
	}
};

for 文で廻しているところがダサいですが、ひとまず表示用の print 関数と、項目をチェックした時の onChecked 関数を作っておきます。
onCheced 関数では、行番号を渡すようにします。

Models を内部に持つようになっていますが、これは後から分離させたいところです。

■GraphicsView を作る

class GraphicsView 
{
public:
	Models *models;
public:
	void print() {
		if ( models == NULL ) return;
		for ( auto it = models->begin(); it != models->end(); ++it ) {
			Model *m = *it;
			cout << m->gridid << &quot;:&quot; << m->selected << endl;
		}
	}
	void onClick( int gridid ) {
		if ( models == NULL ) return ;
		for ( auto it = models->begin(); it != models->end(); ++it ) {
			Model *m = *it;
			if ( m->gridid == gridid ) {
				m->selected = !m->selected;
				break;
			}
		}
	}
};

基本動作は GridView と同じですが、onClick で渡されるのは gridid にしておきます。

■相互伝播を実装する

さて、GridView::onChecked と GraphicsView::onClick が相互に連携してることが分かるので、これを素直に連携させます。

class GrahicsView;
class GridView;
static GraihpcsView *grahpicsView;
static GridView *gridView;

class GridView 
{
public:
	Models *models;
public:
	void print() {
		if ( models == NULL ) return;
		for ( auto it = models->begin(); it != models->end(); ++it ) {
			Model *m = *it;
			cout << m->gridid << &quot;:&quot; << m->selected << endl;
		}
	}
	void onChecked( int row, bool selected ) {
		if ( models == NULL ) return ;
		for ( auto it = models->begin(); it != models->end(); ++it ) {
			if ( row == 0 ) {
				Model *m = *it;
				m->selected = selected;
				graphicsView->onClick( m->gridid ); // NG
				break;
			}
			row --;
		}
	}
};

class GraphicsView 
{
public:
	Models *models;
public:
	void print() {
		if ( models == NULL ) return;
		for ( auto it = models->begin(); it != models->end(); ++it ) {
			Model *m = *it;
			cout << m->gridid << &quot;:&quot; << m->selected << endl;
		}
	}
	void onClick( int gridid ) {
		if ( models == NULL ) return ;
		int row = 0;
		for ( auto it = models->begin(); it != models->end(); ++it ) {
			Model *m = *it;
			if ( m->gridid == gridid ) {
				m->selected = !m->selected;
				graphicsView->onClick( row, m->selected ); // NG
				break;
			}
			row ++;
		}
	}
};

一見、良さそうに見えますが、片方が呼び出されると常にもう片方が呼び出され、再び片方を呼び出すというメッセージの無限伝播に陥ってしまって駄目です。
また、C++ の場合この onClick と onChecked のコンパイルを通すのは結構困難です。クラスをプロトタイプ宣言にしないと駄目ですね。C#ならば、このままでもコンパイルは通りますが、やっぱり、無限にメッセージが伝播してしまいます。
無限に伝播しないようにフラグを付けることも考えられますが、バグの温床となるのでやめておきましょう。ここでは、Model の書き換えをする、そして Model の変更が通知される、という方式に代えていきます。

  • GrahpicsView -> Model の変更
  • GridView -> Model の変更
  • Model -> GraphicsView へ変更通知
  • Model -> GridView への変更通知

というメッセージの流れです。こうすることによって、

  1. GraphicsView::OnClick を呼び出す。
  2. Model が変更される。
  3. Model から GridView へ変更が通知される。
  4. Model から GraphicsView へ変更が通知される。

というフローになります。4番目は通知としては無駄(自分で Model を変更しているので)になりますが、GraphicsView と GridView への対称性を保つためにこのままにしておきます。

■Model の変更通知を実装する

Model クラスは、INotify インターフェースを継承するようにして、関数ポインタとして onChanged を登録させるようにします。

class INotify
{
public:
	void (*onChanged)(void *sender);
	void onProperyChanged( void *sender );
};

class Model : public INotify
{
public:
	int gridid;
	float x;
	float y;
	float z;
protected:
	bool selected;
public:
	bool getSelected() { return this->selected; }
	void setSelected( bool v ) {
		if ( this->selected != v ) {
			this->selected = v;
			if ( onChanged != NULL ) {
				onChanged( this );
			}
		}
	}		
public:
	Model( int id, float x, float y, float z ) {
		this->gridid = id;
		this->x = x;
		this->y = y;
		this->z = z;
		this->selected = false;
	}
};

こうやっておいて、下記のように onChanged に登録したいところなのですが、実際はうまくコンパイルが通りません。

class GridView : public INotify
{
public:
	Models *models;
public:
	GridView() {
		this->onChanged = &GridView::onProperyChanged;	// compile error
	}

	void print() {
		if ( models == NULL ) return;
		for ( auto it = models->begin(); it != models->end(); ++it ) {
			Model *m = *it;
			cout << m->gridid << &quot;:&quot; << m->getSelected() << endl;
		}
	}
	void onChecked( int row, bool selected ) {
		if ( models == NULL ) return ;
		for ( auto it = models->begin(); it != models->end(); ++it ) {
			if ( row == 0 ) {
				Model *m = *it;
				// m->selected = selected;
				m->setSelected( selected );
				break;
			}
			row --;
		}
	}
	void onProperyChanged( void *sender )
	{
		auto model = (Model*)sender;
		cout << &quot;GridView::onChanged: &quot; << model->gridid << endl;	
	}
};

alice018.cpp(56) : error C2440: ‘=’ : ‘void (__thiscall GridView::* )(void *)’ から ‘void (__cdecl *)(void *)’ に変換できません。この変換が可能なコンテキストはありません。

のようなコンパイルエラーがでます。親クラス名が入ってしまうので柔軟性が失われてしまったのです。まぁ、厳密な型をチェックする分にはこちらのほうが良いのではないでしょうか。
また、この onChanged イベントは、ひとつの View に対してしか通知ができません。今回の場合のように、GrapihcView, GridView へ同時に通知するということができません。

__event と __hook を使って mvvm を実装してみる | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3559

なので、今回は素直に __event/__hook を使ってしまいます。

■Model の実装をやり直す

class INotify
{
public:
	__event void onChanged(void *sender);
};
class ITarget
{
public:
	void onPropertyChanged( void *sender );
};

class Model : public INotify
{
public:
	int gridid;
	float x;
	float y;
	float z;
protected:
	bool selected;
public:
	bool getSelected() { return this->selected; }
	void setSelected( bool v ) {
		if ( this->selected != v ) {
			this->selected = v;
			onChanged( this );
		}
	}		
public:
	Model( int id, float x, float y, float z ) {
		this->gridid = id;
		this->x = x;
		this->y = y;
		this->z = z;
		this->selected = false;
	}
};

Model クラスには、__event キーワードを付けて、onChanged イベント登録の準備をしておきます。selected プロパティが変更になったときに、onChanged を呼び出します。
この時点で、View を判断させてもよいのですが、依存関係といしては Model <- View が望ましいので、あえてこういう形にしておきます。

■Model からの通知を View で受ける。

Model で INotify::onChanged を呼び出したときには、ITagert::onPropertyChanged を呼び出すようにします。
GridView, GraphicsView のそれぞれに、onPropertyChanged を実装しておきます。

class GridView : public ITarget
{
public:
	Models *models;
public:
	void print() {
		if ( models == NULL ) return;
		for ( auto it = models->begin(); it != models->end(); ++it ) {
			Model *m = *it;
			cout << m->gridid << &quot;:&quot; << m->getSelected() << endl;
		}
	}
	void onChecked( int row, bool selected ) {
		if ( models == NULL ) return ;
		for ( auto it = models->begin(); it != models->end(); ++it ) {
			if ( row == 0 ) {
				Model *m = *it;
				// m->selected = selected;
				m->setSelected( selected );
				break;
			}
			row --;
		}
	}
	void onPropertyChanged( void *sender )
	{
		auto model = (Model*)sender;
		cout << &quot;GridView::onChanged: &quot; << model->gridid << endl;	
	}
};

class GraphicsView : public ITarget
{
public:
	Models *models;
public:
	void print() {
		if ( models == NULL ) return;
		for ( auto it = models->begin(); it != models->end(); ++it ) {
			Model *m = *it;
			cout << m->gridid << &quot;:&quot; << m->getSelected() << endl;
		}
	}
	void onClick( int gridid ) {
		if ( models == NULL ) return ;
		for ( auto it = models->begin(); it != models->end(); ++it ) {
			Model *m = *it;
			if ( m->gridid == gridid ) {
				// m->selected = !m->selected;
				m->setSelected( !m->getSelected() );
				break;
			}
		}
	}
	void onPropertyChanged( void *sender )
	{
		auto model = (Model*)sender;
		cout << &quot;GraphicsView::onChanged: &quot; << model->gridid << endl;	
	}
};

■バインドする

__hook キーワードを使って、Model::onChanged と、GridView::onPropertyChanged をバインドしていきます。
Model の通知先は、2 の View になるので、__hook を 2回定義します。

int main()
{
	models = new Models();
	graphicsView = new GraphicsView();
	gridView = new GridView();
	graphicsView->models = models;
	gridView->models = models;

	for ( int i=0; i<10; i++ ) {
		auto model = new Model(i,0.0,0.0,0.0);
		__hook( &Model::onChanged, model, &GridView::onPropertyChanged, gridView );
		__hook( &Model::onChanged, model, &GraphicsView::onPropertyChanged, graphicsView );
		models->push_back( model );
	}
	graphicsView->onClick( 2 );
	gridView->print();

	gridView->onChecked( 8, true );
	gridView->print();
}

ここでは、Model の selected プロパティから直接変更通知を受けるために、すべての model に対して onChanged を登録します。
ただ、これだと面倒なので、vector::onChanged に登録して、複数の model を一括に扱うようにしたいですよね。

■実行結果

実行結果はこんな感じです。
最初は、GraphicsView -> Model -> GridView へのデータ伝播。二番目のものは、GridView -> Model -> GraphicsView での伝播ですね。

D:\work\blog\src\alice>alice018
GraphicsView::onChanged: 2
GridView::onChanged: 2
0:0
1:0
2:1
3:0
4:0
5:0
6:0
7:0
8:0
9:0
GraphicsView::onChanged: 8
GridView::onChanged: 8
0:0
1:0
2:1
3:0
4:0
5:0
6:0
7:0
8:1
9:0

あえて、ViewModel, Controller を使わずに書いていますが、このバインドの部分は ViewModel としても良いはずです。
Model::onChanged イベントに View の型が入ってこないので、どんな View が来ても(あるいは View がなくても) Model を変える必要がありません。
そういう意味ではそれなりに独立性が高いかなと。

カテゴリー: C++ | 2つのViewの変更が、同時に行われるときのModelの挙動を考察 はコメントを受け付けていません

Visual C++ の resource.h を考え直す

何故今頃になって、*.rc やら resource.h やらの話になるかというと(誰も分からない/困らないというのはさておき)、

.NET におけるアプリケーション アーキテクチャ ガイダンス
http://www.microsoft.com/ja-jp/dev/2010/solutions/architecture/default.aspx

をざっと読み返していて、かつてのVB2.0やらVC++5(あたり?)から基本的な「レイヤー」という概念は変わっていない、というところを考えていたわけです。当時(主に15年前)は、メモリーの制約が大きかったり、回線が細すぎたり(それでもパソコン通信の9600bpsに比べれば、社内LANの10Baseは天国だった訳で)する「制約」があり、このためにもメモリ効率やCPUのリソースを効率的かつ直接的に扱う C/C++ が好まれたわけです。VM という意味では、Pcode を使った VB2.0 が既に出ているので「実運用」に耐えることができた(当時、Excel VBAで業務アプリケーションを組んでいたし)、ちょうどそんな狭間の時期ですよね。いやいや、既に smalltalk や lisp があったので、スクリプト言語という概念も普通にあったわけで。emacs を使っていたし、もっと進んでいたハズ。

オブジェクト指向の概念はまだ「パターン」という名前に発展はしていませんでしたが、既にありました。が、好意的に言えば「まだ、こなれていなかった」あるいは「現実解を選んだ」のが、Microsoftが生み出したMFCというクラスライブラリ群と *.rc を中心とするデザイナです。wysiwyg という奴ですね。

ディスプレイ上でマウスを使いながらぽちぽちと部品(ボタンコントロール)などを配置します。今となっては当然の作りになっていますが(いや、Qtとかtcl/tkとか使うと座標指定している?)、VB2.0 が出た頃は「これは便利」と皆さま相当使ったものです。
さて、VB で好評を博したこのインターフェースを、VC に持ってきたのが Visual Studio のリソースエディタなのですが、今から考えれば「コントロールは名前で制御すればよかったッ!!!」って具合でしょうか?

VB の場合、画面リソースは、

VERSION 5.00
Object = "{648A5603-2C6E-101B-82B6-000000000014}#1.1#0"; "MSCOMM32.OCX"
Begin VB.Form Form2
   Caption         =   "Terminal"
   ClientHeight    =   3450
   ClientLeft      =   60
   ClientTop       =   345
   ClientWidth     =   3600
   LinkTopic       =   "Form2"
   ScaleHeight     =   3450
   ScaleWidth      =   3600
   StartUpPosition =   3  'Windows の既定値
   Begin VB.PictureBox Pic_Info
      Height          =   255
      Left            =   3120
      ScaleHeight     =   195
      ScaleWidth      =   315
      TabIndex        =   9
      Top             =   3120
      Width           =   375
   End
   Begin VB.CommandButton IDB_DISCONNECT
      Caption         =   "Disconnect"
      Height          =   255
      Left            =   120
      TabIndex        =   8
      Top             =   2760
      Width           =   975
   End
...
End

のように、「Pic_Info」や「IDB_DISCONNECT」という名前(文字列)で管理しています。この名前の namespace は、このフォームの中で有効です。なので、他のフォームを作ったときにも、同じ「Pic_Info」や「IDB_DISCONNECT」という名前を付けられるのです。これらの区別は、Form1.Pic_Info と Form2.Pic_Info とは別のものとして扱われます。当たり前といえば当たり前のような気がするのですが…

VC++ の場合はどうでしょうか? *.rc のダイアログリソースを除いてみると。

IDD_HC_WAVE_VRETICAL_PAGE DIALOGEX 0, 0, 279, 193
STYLE DS_SETFONT | WS_CHILD | WS_DISABLED | WS_CAPTION
CAPTION "Vertical"
FONT 8, "MS Sans Serif", 0, 0, 0x0
BEGIN
    LTEXT           "Group name:",IDC_STATIC,5,10,77,8
    LTEXT           "Hatch cover no:",IDC_STATIC,5,26,77,8
    LTEXT           "Length of hatch opening (m):",IDC_STATIC,5,42,101,8
    LTEXT           "Breadth of hatch opening (m):",IDC_STATIC,5,57,101,8
    LTEXT           "Hatch center location FP (m):",IDC_STATIC,5,74,101,8
    LTEXT           "Pressure P (kN/m2):",IDC_STATIC,5,91,101,8
    EDITTEXT        IDC_E_HC_WAVE_LOADS_GROUP_NAME,114,10,92,12,ES_AUTOHSCROLL
    EDITTEXT        IDC_E_HC_WAVE_LOADS_BREADTH,114,57,92,12,ES_AUTOHSCROLL
    EDITTEXT        IDC_E_HC_WAVE_LOADS_HATCH_COVER_NO,114,26,92,12,ES_AUTOHSCROLL
    EDITTEXT        IDC_E_HC_WAVE_LOADS_LENGTH,114,42,92,12,ES_AUTOHSCROLL
    EDITTEXT        IDC_E_HC_WAVE_LOADS_CENTER,114,73,92,12,ES_AUTOHSCROLL
    EDITTEXT        IDC_E_HC_WAVE_LOADS_PRESSURE,114,89,92,12,ES_AUTOHSCROLL
END

「IDC_E_HC_WAVE_LOADS_GROUP_NAME」や「IDC_E_HC_WAVE_LOADS_BREADTH」は一見名前のように見えますが、別途 resource.h で定義されている数値なのです。

#define IDC_E_HC_WAVE_LOADS_GROUP_NAME 1000
#define IDC_E_HC_WAVE_LOADS_BREADTH    1001

で、この数値を使って VC++ の場合は、テキストボックスの CEdit を扱います。

CEdit *editName = (CEdit*)this->GetDlgItem(IDC_E_HC_WAVE_LOADS_GROUP_NAME);

あるいは

CEdit m_editName;
...
DDX_Control( ex, IDC_E_HC_WAVE_LOADS_GROUP_NAME, editName );

まあ、これはこれで良いのですよ。文字列のようにメモリを喰う(あるいは定義しにくい値)を使うよりは、数値で受け渡しができると高速化できるし。実際、私も当時は、GetDlgItem を使って CButton や CEdit にキャストをしていました。
が、問題はこの数値の定義が、resource.h に集まってしまう(あるいは何処かで define しなければいけない)というのがあります。

VB の場合は、フォーム毎に「Pic_Info」という文字列を定義しているし、コントロールを特定するにしても「Pic_Info」という文字列と比較するので、他のファイルなどを参照する必要はありません。勿論「Pic_Info」という文字列を「Picutre_Info」のようにタイプミスしてしまう問題もありますが、VB の場合は、コントロールの名前=コントロール自身を示す実装となっているため、このタイピングミスが発生しません。

ですが、VC++ の場合、一見 VB のデザイナを継承した作りになっていますが、resource.h に一度定義した値を *.rc の値とマッピングさせているために、二度手間が発生しています。さらに、namespace の概念は、resource.h 全体に広がってしまうので、アプリケーションで使う全フォームで別々の数値を割り振る必要があります。更に云えば、C++ では #define で数値を定義することが多く、resource.h に定義されているものは、画面以外のリソース ID も含んでいるのです。実に混乱の元となっています。

コンパイル時の弊害もあって、resource.h に書かれている定義を数々のフォームで参照するために、何処かの画面を変更するたびに、resource.h が書き換えられて、他の「変更していない画面」もコンパイルされてしまうことです。これは、C++ のコンパイル時の構造上の欠陥でもあるのですが、実はフォームごとに resource.h を用意する(あるいは、フォームのヘッダファイルにコントロールIDを直接定義する)ということで回避できた要素なので、ええ「仕様ミス」ですね。
当時としては、十数画面が普通だったので(そうしないとメモリが足りなくなった)問題がないのですが、今となって百画面あっても不思議ではないので(そういう作り方をするのも問題ですが)、結構コンパイルに時間がかかります。ひょっとすると、当時の VC++ よりもコンパイルは遅くなっているかもいしれません。
(当時、MSC++ はコンパイルが遅いので有名だったので、正確なところはわかりませんが。そうそう、toubo c++ が早かった。gcc は遅かった)。

では、XAML の場合はどうなっているかというと、コントロールの識別は文字列(Name属性)として扱います。また、デザイナ&コンパイラは、VB と同じように、文字列とコントロール名を自動的に相互変換してくれます。
ちなみに、Xcode + objective-c の場合は、Outlet という形で手動でデザイナとマッピングさせています。

一応、当時の VC++ を擁護すると、それぞれのコントロールは window handle に結び付けられているので、これを作るときに int 型の値を使ったほうが効率が良かったのですよ。metro style をはじめとする silverlight や WPF などの XAML の場合には window handle を持たずに、直接 XAML の構造から検知をします。なので、この構造の違い(window class struct の違い)が、ここの「違い」になっています。
とはいえ、VB の場合は、名前で相互変換してるわけだから、VC++ の場合もそれを踏襲すればよかったのに…愚痴愚痴。という具合です。

なので、MFC 由来の CButton や CEdit という標準コントロールクラスと、XAML で提供されるコントロール(System.UI)とは相性が悪い、ってことなんですかねぇ。ひたすら、VC++ が XAML を扱えないのは、MFC 自体がネックになっているのかもしれません。
そういう意味では、c++/cx から MFC をひっぺはがして、WinRT という COM で再構築をして、便利クラス部分(Platform::String関係)とUI部分(Windows::UI::Xaml関係)だけを残すというのは利に叶っているような気がします。

で、

まぁ、metro style の場合は必要であれば c++/cx で組む(OpenCV, Fortran の組み合わせとか)選択肢もあるのですが、じゃあ desktop はどうするのか?ってことで、c++/cli を使い続けるのか(インテリセンスが効くようになったし)、画面部分は C# に任せて、コアなところは MFC を含むVC++ で組むのか、ということになります。更に、既存のアプリに新しい画面を追加するのは、この resource.h と付き合っていかなければならないのか?ってことになります。まぁ。レガシーといえばレガシーなわけですが。

XAML 時代のアイデアを借用して、デザイナ自体を C# で作り、そして C++ のコードを吐き出させるという逆向きのやり方もありかな、と思っています。C++ だけで、画面を wisywig で作成した後に、*.rc, resource.h かつ、フォーム関連のヘッダファイルを整合性があうように弄る、というのは結構な手間なのですが、これを C# でやろうとすれば効率よく書けるでしょう。特にデザイナ部分は楽かも、と思ってしまいます。

ざっとアイデアを書いておくと、

方針としては、VB, XAML のように「文字列」で各コントロールの名前付けをする、ってことです。そうして、フォーム単位で namespace が有効になるような構成に仕立て直すのです。
既存の CButton, CEdit はそのまま流用できたほうがよいので、これを拡張できるようにしていきます。

class Form1
{
public:
	static int IDD				    = (int)"Form1";
	static int IDC_GROUP_NAME 		= (int)"GroupName";
	static int IDC_LOADS_BREADTH    = (int)"Breadth";
protected:
	CEdit GroupName(IDC_GROUP_NAME);
	CEdit Breadth(IDC_LOADS_BREADTH);
...
};

C++ の場合、値の初期化が直接できないので、考え直す必要がありますが、上記のように文字列をint型に直しておきます。クラス内で定義するので、定義自体は、Form1::IDC_GROUP_NAME としてアクセスします。ここの文字列と、コントロールの名前は「CEdit(“GroupName”)」のように一致させます。ここでは、CEdit(IDC_GROUP_NAME) のようにして値を設定しています。

リソースエディタのほうは、以下のように namespace を指定するか、

Form1::IDD DIALOGEX 0, 0, 279, 193
STYLE DS_SETFONT | WS_CHILD | WS_DISABLED | WS_CAPTION
CAPTION "Vertical"
FONT 8, "MS Sans Serif", 0, 0, 0x0
BEGIN
    LTEXT           "Group name:",IDC_STATIC,5,10,77,8
    LTEXT           "Hatch cover no:",IDC_STATIC,5,26,77,8
    EDITTEXT        Form1::IDC_GROUP_NAME,114,10,92,12,ES_AUTOHSCROLL
    EDITTEXT        Form1::Breadth,114,57,92,12,ES_AUTOHSCROLL
END

次のように、直接文字列を指定してマッチングを取ります。

"Form1" DIALOGEX 0, 0, 279, 193
STYLE DS_SETFONT | WS_CHILD | WS_DISABLED | WS_CAPTION
CAPTION "Vertical"
FONT 8, "MS Sans Serif", 0, 0, 0x0
BEGIN
    LTEXT           "Group name:",IDC_STATIC,5,10,77,8
    LTEXT           "Hatch cover no:",IDC_STATIC,5,26,77,8
    EDITTEXT        "GroupName",114,10,92,12,ES_AUTOHSCROLL
    EDITTEXT        "Breadth",114,57,92,12,ES_AUTOHSCROLL
END

ただし、リソースの場合は、リソースコンパイラが動くのでファイル構造は変えられないので、上記の場合は適当なプリプロセッサを通して名前を変換するのがよいかと。

一度 resource.h に直すのもアリなのですが、依存関係が Form1 <- resource.h になりそうなので、パスですかねぇ。
C/C++ の場合、同じ文字列は同じアドレスに格納されるので、コントロールの名前は strcmp をせずに int 型の比較(正確に言えばポインタの直接比較)で同定ができます。

まあ、実際作るかどうかは別として、レガシー MFC/C++ のアプリの改修に、resource.h のコンパイル地獄で時間を取られるのは嫌なので、コントロールを文字列で同定できるハッキングはしたいなぁ、と考えています。つーか、やらないと駄目かも。

カテゴリー: C++ | Visual C++ の resource.h を考え直す はコメントを受け付けていません

C#のぴよぴよコードをなんとか使える形にするための2つの方法

ク●コード改め「ぴよぴよコード」なのですが、目の前にあるク●コード改め「ぴよぴよコード」をなんとか使いこなすための思考実験。
ええ、思考実験なので業務コードの場合は業務コードにあわせたカスタマイズが必要になる訳ですが。

基本は「リファクタリング」するあるいは「リライト」するのが良いのですが事情が許さないことが多々あります。

  • できあがったアセンブリに手を付けてはいけないという縛りがある。
  • コードが「ぴよぴよ」すぎて、ロジックが訳わからん。
  • リライトしてテストしてという時間がコードが複雑すぎてできない。

「手を付けてはいけない」のほうは、政治的な問題が絡むので、できることならば手を付ける方向に動くようマネジメントすればよいのですが、後の二つはちょっと単純にリファクタリングなりリライト(書き直し、作り直し)などをしている時間が、納期的や能力的(既存のコードを理解するという意味で)がちょっとない時に使います。
まぁ、基本は「動いているものには手を付けない」という古い思想を守るパターンということですね。このあたり、経済的に「古い思想を守る」のが割が合わなくなったら、全面的にリライト、リメイクするわけです。そのあたりの判断基準の話は後日。

■もともとのパターン

サンプルとして作った Piyo クラスがこれです。

public class Piyo
{
	public void Plus( int x, int y, ref int ans )
	{
		// わざわざリファレンスで答えを返している
		ans = x + y;
	}
	public void PlusMuins( int x, int y, ref int ans1, ref int ans2 )
	{
		// 戻り値が2つあるので、ref で返している
		ans1 = x + y;
		ans2 = x - y;
	}
}

何故か、戻り値を使わずに ref で返しています。2つ答えがある場合は2つの ref を使います。

// ref を多用しているパターン
private void button1_Click(object sender, EventArgs e)
{
	Piyo piyo = new Piyo();
	int x = 10;
	int y = 20;
	int ans = 0;
	piyo.Plus(x, y, ref ans);

	int ans1 = 0;
	int ans2 = 0;
	piyo.PlusMuins(x, y, ref ans1, ref ans2);
}

統一性が取れているように見えるのですが、オブジェクト指向的にはちょっとアレですよね。特に C# の場合は呼び出し時にも ref をつけないと駄目なので結構面倒です。
C++ の場合は、&ans1 のようにポインタ渡しをすることがよくあるのですが、できればオブジェクトとして独立性を高くしておきたいところ。

■C#の拡張メソッドを使う

拡張メソッド (C# プログラミング ガイド)
http://msdn.microsoft.com/ja-jp/library/bb383977(v=vs.90).aspx

C# 3.0 の頃からある拡張メソッドの機能で Piyo クラスを拡張してしまいます。

// Piyo クラスを拡張する
public static class PiyoExtention
{
	static public int Plus(this Piyo piyo, int x, int y)
	{
		// 戻り値を使うように変更
		int ans = 0;
		piyo.Plus(x, y, ref ans);
		return ans;
	}

	static public Ans PlusMuins(this Piyo piyo, int x, int y)
	{
		// 2つの答えはオブジェクトで返す
		var ans = new Ans();
		piyo.PlusMuins(x, y, ref ans.plus, ref ans.muins);
		return ans;
	}
	public class Ans
	{
		public int plus;
		public int muins;
	}
}

PiyoExtention クラスを作るときに多少手間ですが、利用するときには普通のクラスっぽくなるので便利かと。
これは既存のアセンブリに手を付けられないときに活用できます。

// 拡張メソッドを使うパターン
private void button2_Click(object sender, EventArgs e)
{
	// ひとつの ref は戻り値にする
	var piyo = new Piyo();
	int x = 10;
	int y = 20;
	int ans = piyo.Plus(x, y);

	// ふたつ以上ある場合はオブジェクトで返す
	PiyoExtention.Ans ans1 = piyo.PlusMuins(x, y);
}

答えが二つある場合は、オブジェクト/構造体で返しています。このあたり、戻り値のデータの受け渡しをオブジェクト経由でやるのか、プロパティ経由でやるのかという2つのスタイルがあるのですが、拡張メソッドの場合はプロパティを作れないのオブジェクトで返します。
強引に、プロパティを作れなくもないのですが、拡張メソッド自体がややこしい作りになるのでパスしたほうが良いかと。

■強引に継承して拡張する

もうひとつのパターンは、クラス自体を継承して拡張する方法ですね。
完璧にやろうとすると、全てのメソッドやプロパティを継承させることになって大変手間なのですが、必要な部分だけを継承して、その他の部分は内部で持っている _piyo オブジェクトをそのまま公開してしまいます。

// ちまちまと継承するパターン
private void button3_Click(object sender, EventArgs e)
{
	var piyo = new PiyoEx(new Piyo());
	int x = 10;
	int y = 20;
	int ans = piyo.Plus(x, y);

	var ans2 = piyo.PlusMuins(x, y);
	int plus = ans2.plus;
	int muins = ans2.muins;
}

ここでは、2つ以上の答えを返す場合には、試しに無名オブジェクトを作って dynamic にして返しているのですが、インテリセンスが効かないのであまり多用しないほうがいいかも。

// Piyo クラスを継承する
public class PiyoEx
{
	// 他でも使えるように public にしておく
	public Piyo _piyo;
	// コンストラクタ
	public PiyoEx(Piyo piyo)
	{
		_piyo = piyo;
	}

	public int Plus(int x, int y)
	{
		int ans = 0;
		_piyo.Plus(x, y, ref ans);
		return ans;
	}
	public dynamic PlusMuins(int x, int y )
	{
		int ans1 = 0;
		int ans2 = 0;
		_piyo.PlusMuins(x, y, ref ans1, ref ans2);
		// 無名オブジェクトを利用してみる
		return new { Puls = ans1, Muins = ans2 };
	}
}

ここでは、1つのクラスしか継承していませんが、ごちゃごちゃした 純粋オブジェクト指向 Java 風の 3,4個あるクラスをひとまとめにしてしまって便利クラスを作っておくのも手です。オブジェクト指向的にはどうかという意見もあるでしょうが、業務の質に合わせていくのが「経済的」ですよ、ということで。

カテゴリー: C# | 2件のコメント

[c++] __event と __hook を使って mvvm を実装してみる

__eventを使ったイベントハンドリング – CREST’S WEBLOG (」・ω・)」うー!(/・ω・)/にゃー!
http://d.hatena.ne.jp/Crest/20100418/1271603367
__event
http://msdn.microsoft.com/en-us/library/cb1dzt8t(v=vs.100).aspx

MS-C++ では __declspec(property()) でプロパティを作れるよ | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3557

を書いてみて、ひょっとすると event/delegate 系も使えるようになっているのでは?と思ったらありました。
__event/__hook の組み合わせでいけます。C# の event/delegate, Action<>と機能は同じ。

#include &quot;stdafx.h&quot;
#include <afxwin.h>
#include <iostream>
#include <string>
using namespace std;

// Model側のインターフェース(INotify)
class INotifyProperyChange
{
public:
	__event void PropertyChange( INotifyProperyChange *model, void *name, void *value, const type_info &info );
};

// View側のインターフェース(ITarget)
class ITargetPropertyChanged
{
public:
	void OnProperyChanged( INotifyProperyChange *model, void *name, void *value, const type_info &info );
};

class Model : public INotifyProperyChange
{
private:
	int m_num;
	string m_name;
public:

	__declspec(property(get=getNum, put=setNum)) int Num;
	int getNum() { return m_num ; }
	void setNum( int value ) {
		m_num = value;
		PropertyChange( this, &quot;Num&quot;, &value, typeid(value) );
	}

	__declspec(property(get=getName,put=setName)) string Name;
	string &getName() { return m_name; }
	void setName( string &value ) {
		m_name = value;
		PropertyChange( this, &quot;Name&quot;, &value, typeid(value) );
	}

};

// View は Model から独立している
class View : public ITargetPropertyChanged
{
private:
	int m_num;
	string m_name;
public:
	__declspec(property(get=getNum, put=setNum)) int Num;
	int getNum() { return m_num; }
	void setNum( int value ) {
		m_num = value;
		cout << &quot;View::Num: &quot; << value << endl;
	}
	__declspec(property(get=getName,put=setName)) string Name;
	string &getName() { return m_name; }
	void setName( string &value ) {
		m_name = value;
		cout << &quot;View::Name: &quot; << value << endl;
	}
public:
	void OnProperyChanged( INotifyProperyChange *model, void *name, void *value, const type_info &info )
	{
		// ここを map にする?
		string propName((const char*)name);
		if ( propName == string(&quot;Num&quot;)) {
			this->Num = *(int*)value;
		} else if ( propName == string(&quot;Name&quot;)) {
			this->Name = *(string*)value;
		}
	}
};

// ViewModel は View と Model の両方に依存しているが、
// View のほうへの依存度が高い
class ViewModel
{
private:
	View *m_view;
	Model *m_model;
public:
	void OnProperyChanged( INotifyProperyChange *model, void *name, void *value, const type_info &info )
	{
		m_view->OnProperyChanged( model, name, value, info );
	}
	void Bind( Model *model, View *view )
	{
		this->m_model = model;
		this->m_view = view;
		__hook( &Model::PropertyChange, model, &ViewModel::OnProperyChanged, view );
	}
};

int _tmain(int argc, _TCHAR* argv[])
{
	Model model;
	View view;
	ViewModel vm;

	vm.Bind( &model, &view );
	model.Num = 10;
	model.Name = string(&quot;masuda tomoaki&quot;);
}
D:\work\blog\src\CppProperty\Debug>CppProperty.exe
View::Num: 10
View::Name: masuda tomoaki

ざっと書くとこんな感じ。C# の一般的な mvvm よりも、Model と View の独立性を強くしてあります。__hook の利用法が面白いところで、__hook( ソースの関数ポインタ、ソースオブジェクト、ターゲットの関数ポインタ) を設定します。ここで仕様バグなのかどうかは不明ですが、ターゲットの関数ポインタは、何も自分のクラスのメソッドである必要はありません。ここでは &ViewModel::OnProperyChanged と自分自身を示していますが、&View::OnProperyChanged のほうに他クラスの関数ポインタも示せるという優れもの…と言いますかいい加減な実装がw。ソースの関数メソッドとターゲットの関数メソッドの「型があっていればOK」といういい加減な実装(意図的?)なために、かなり自由度が高いのです。

これを試しに interface だけ(class継承だけ)を使うと、どうしてもうまく行きません。関数ポインタの型がきつくて、一度 static 関数を通さないと実装ができないという状態なので、__hook は重宝します。
まあ、考えてみれば、c/c++ なのだからインラインアセンブラを作れば良いのでした。なるほど。

さて、これでコマンドラインでは、c++ で(おそらく ms-c 限定ですが) mvvm の INotiry, ITarget が動くことが分かりました。今度は windows form(内部的には *.rc)を使って実装してみましょうか。DDX_Control マクロと UpdateData 関数を使わずに実装できるかも。

そうそう View::OnProperyChanged の実装がいまいちなので、View のプロパティ名=バインド名の部分をなんとかしたいですね。C# ならばリフレクションを使うところですが、C++ の場合はどうするか思案中。

カテゴリー: C++ | [c++] __event と __hook を使って mvvm を実装してみる はコメントを受け付けていません