MonaForms/delegate の変更点



 [[MonaForms]]
 
 * MonaFormsのdelegateについて [#z08a17c6]
 - メンバ関数に対するコールバック
 - MonaFormsで_Pや_Aに次いで嫌われている部分
 
 ** コールバック [#ce7be6fc]
 - 実行時に処理を変更したりするためにC言語で使われる手法。
 - 関数をポインタに代入して呼び出すこと。
 
 *** 実例 [#iadf0ff9]
  #include <stdio.h>
  void test() {
 	printf("test\n");
  }
  int main() {
 	void(*func)() = test;
 	(*func)();
 	return 0;
  }
 - funcというポインタにtest()という関数を代入して、func経由でtest()を呼び出している。
 - 呼び出しの部分はfunc()と書いても受け付けてもらえるが、コールバック(関数ポインタ経由の呼び出し)であることを明示するため、(*func)()のような書き方をする方が良い。
 - 定義の部分を比べてみると(下記)、(*func)とtestが対等だということが分かるので、やはり(*func)と書くべきである。
  void test()
  void(*func)()
 - 一見意味不明な書式だが、(*名前)という定義の仕方に注目すると把握しやすい。引数などがあっても同じ。
  int test(int a, int b, int c);
  int(*func)(int a, int b, int c) = test;
 
 ** メンバ関数に対するコールバック [#pa2641a9]
 さて、C++。
 
 *** 何も考えずにクラス化 [#f3b3fdee]
  #include <stdio.h>
  class Test {
  public:
 	void test() {
 		printf("test\n");
 	}
  };
  int main() {
 	void(*func)() = Test::test;
 	(*func)();
 	return 0;
  }
 コンパイルしてみると・・・
  $ g++ test.cpp
  test.cpp: In function `int main()':
  test.cpp:9: error: invalid use of non-static member function `void Test::test()'
  test.cpp:9: error: invalid use of non-static member function
 そうは問屋が卸さないという感じ。(泣
 
 *** static [#gc08372b]
 non-staticだと文句を付けられているので、staticに変更してみる。
  #include <stdio.h>
  class Test {
  public:
 	static void test() {
 		printf("test\n");
 	}
  };
  int main() {
 	void(*func)() = Test::test;
 	(*func)();
 	return 0;
  }
 問題なく動く。
  $ g++ test.cpp
  $ ./test
  test
 
 *** インスタンス [#e7996e25]
 だがしかし、staticだとインスタンスメンバにアクセスしたいときに困る。とりあえず引数として渡してみる。
  #include <stdio.h>
  class Test {
  private:
 	int no;
  public:
 	Test(int n) : no(n) {}
 	static void test(Test* p) {
 		printf("%d\n", p->no);
 	}
  };
  int main() {
 	Test a(1), b(2);
 	void(*func)(Test*) = Test::test;
 	(*func)(&a);
 	(*func)(&b);
 	return 0;
  }
 問題なく動く。
  $ g++ test.cpp
  $ ./test
  1
  2
 
 *** 正規の書式 [#ta6ce172]
 実はインスタンスメンバにコールバックさせる正規の書式がある。
  #include <stdio.h>
  class Test {
  private:
 	int no;
  public:
 	Test(int n) : no(n) {}
 	void test() {
 		printf("%d\n", no);
 	}
  };
  int main() {
 	Test a(1), b(2);
 	void(Test::*func)() = &Test::test;
 	(a.*func)();
 	(b.*func)();
 	return 0;
  }
 - なんじゃこりゃ?という書式。
 - 型(クラス名)の限定が付いて、インスタンス経由で呼び出す必要があるというのが違う。
 
 *** 型縛り [#pd824702]
 型が限定されるので、次の2つに互換性がない。
  class Test1 {
  public:
  	void test() { printf("Test1\n"); }
  };
  class Test2 {
  public:
  	void test() { printf("Test2\n"); }
  };
 - Test1::testとTest2::testを同一の関数ポインタに代入することができない。
 
 *** 仮想関数 [#yec54f14]
 同一の関数ポインタで扱う場合、仮想関数を使うしかない。
  #include <stdio.h>
  class Test {
  public:
 	virtual void test() = 0;
  };
  class Test1 : public Test {
  public:
 	virtual void test() { printf("Test1\n"); }
  };
  class Test2 : public Test {
  public:
 	virtual void test() { printf("Test2\n"); }
  };
  int main() {
 	Test *a = new Test1(), *b = new Test2();
 	void(Test::*func)() = &Test::test;
 	(a->*func)();
 	(b->*func)();
 	return 0;
  }
 実行結果
  $ g++ test.cpp
  $ ./test
  Test1
  Test2
 - これはやろうと思えば出来るという類のことで、実際にはほとんど意味がない。
 - 仮想関数を実装するためには基本クラスで宣言して同じ名前を付ける必要がある。C言語でのコールバックのように戻り値と引数の型さえ合っていれば名前に制限がないのと大違い。
 - そもそも型が限定される時点で、インスタンスを渡して普通に関数を呼んだ方が早い。以下の例ではaとbが同じTest*として扱われ、動作結果が違うということに意味がある。
  a->test();
  b->test();
 -- これが多態による方法。
 -- 別々の動作を後で設定する場合、同一のインターフェースを実装したインスタンスを渡して固有の動作をさせる。
 
 *** テンプレート併用コールバック [#x3a48d08]
 - MonaFormsのdelegateの基本となる方法。
 - C言語のコールバックは関数の戻り値と引数さえ同じなら良いので、継承を考えなくて良いし名前も自由に付けられる。
 - それをテンプレートによって実現しようというアイデア。
 
  #include <stdio.h>
  class IDelegate {
  public:
 	 virtual void Invoke() = 0;
  };
  template <class T> class Delegate : public IDelegate {
  private:
 	T* target;
 	void(T::*func)();
  public:
 	Delegate(T* t, void(T::*f)()) : target(t), func(f) {}
 	virtual void Invoke() { (target->*func)(); }
  };
  class Test1 {
  public:
 	void foo() { printf("Test1\n"); }
  };
  class Test2 {
  public:
 	void bar() { printf("Test2\n"); }
  };
  int main() {
 	Test1 a;
 	Test2 b;
 	IDelegate* aa = new Delegate<Test1>(&a, &Test1::foo);
 	IDelegate* bb = new Delegate<Test2>(&b, &Test2::bar);
 	aa->Invoke();
 	bb->Invoke();
 	return 0;
  }
 実行結果
  $ g++ test.cpp
  $ ./test
  Test1
  Test2
 - 相互に継承関係のないTest1とTest2のインスタンスメンバを、IDelegateという同一の型としてコールバックさせている。
 - C言語と同じように、戻り値と引数の型さえ同じであれば、継承関係や名前の縛りがない。
 - Microsoftがこの手の拡張をJavaに施したためSunに訴えられた。(→[[参考情報>http://java-house.jp/ml/archive/j-h-b/020252.html]])
 -- Javaでは直接無名クラスを生成できるので、名前を付ける必要がなく、delegateの意義は疑問。(→[[参考情報>http://java-house.jp/ml/archive/j-h-b/019792.html]])
 -- C++ではJavaのような書き方が出来ないが、gcjの実戦投入が目前に迫った今、C++に拘っても仕方ないかも・・・
 
 ** アンケート [#g1e97598]
 この記事についてのご感想をお寄せください。
 #vote(意味不明[0],既に知ってる[5],勉強になった[21],多態でいいじゃん[0],C言語でいいじゃん[0],Javaでいいじゃん[1],プログラミング自体やらない[0])
 #vote(意味不明[1],既に知ってる[5],勉強になった[21],多態でいいじゃん[0],C言語でいいじゃん[0],Javaでいいじゃん[1],プログラミング自体やらない[0])

リロード   新規 編集 差分 添付 複製 改名   トップ 一覧 検索 最終更新 バックアップ   ヘルプ   最終更新のRSS

Modified by mona
PukiWiki 1.4.6 Copyright © 2001-2005 PukiWiki Developers Team. License is GPL.
Based on "PukiWiki" 1.3 by yu-ji
Powered by PHP 5.2.17
HTML convert time to 0.005 sec.