C++型消去:派生クラス型を引数に取る関数オブジェクトを基本クラスに登録して呼び出す †
次のようなコードを考えます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
-
-
|
!
-
|
-
-
|
|
-
!
|
-
|
|
!
|
-
-
!
|
-
|
!
-
-
!
|
|
-
!
|
!
-
|
!
-
|
!
-
|
!
-
|
!
-
|
-
!
|
-
|
|
-
!
|
|
|
|
-
!
!
|
-
!
-
-
!
!
|
|
-
!
|
-
|
!
|
-
!
|
-
|
!
|
-
!
|
-
|
!
|
|
|
!
!
|
-
!
-
-
!
-
!
-
!
!
| #include <functional>
#include <string>
#include <memory>
#include <iostream>
namespace
{
class Character
{
protected:
Character() { ]
public:
virtual ~Character() { }
template<class TSelf, class TFuncObj>
void setFunc(TFuncObj func)
{
}
void doFunc()
{
}
private:
Character(const Character&);
void operator=(const Character&);
};
class Suika : public Character { };
class Meiling : public Character { };
class Tenshi : public Character { };
class Controller
{
public:
template<class TChara>
void initialize()
{
std::auto_ptr<TChara> chara(new TChara());
chara->setFunc<TChara>(
std::bind1st(
std::mem_fun1(&Controller::procCallback<TChara>),
this));
_chara = chara;
}
void execute()
{
_chara->doFunc();
}
private:
template<class TChara>
void procCallback(TChara*)
{
std::cout << "(Basic function)" << std::endl;
}
template<>
void procCallback(Suika*)
{
std::cout << "Ibuki Suika" << std::endl;
}
template<>
void procCallback(Meiling*)
{
std::cout << "Chugo... Hong-Meiling!!" << std::endl;
}
private:
std::auto_ptr<Character> _chara;
};
}
int main(int, char**)
{
Controller ctrl;
ctrl.initialize<Suika>();
ctrl.execute();
ctrl.initialize<Meiling>();
ctrl.execute();
ctrl.initialize<Tenshi>();
ctrl.execute();
return 0;
}
|
長ったらしくてわかりにくいですが、要するに…
- 66行目で派生クラス型を引数に取る関数オブジェクトを事前に登録し、
- 79行目で事前に登録した関数オブジェクトを呼び出す。
…という処理を基本クラス側の実装で実現するにはどうすればいいのかということです。
実行結果としては次のような出力を期待しています。
Ibuki Suika
Chugo... Hong-Meiling!!
(Basic function)
解法としては、 boost::function
等にも使われている型消去(Type Erasure)というテクニックが使えます。
関数オブジェクト型がコピー可能であると仮定した時の Character
クラスの実装例は次の通りです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
| -
!
-
|
-
!
|
-
-
!
-
|
|
!
|
-
!
-
|
!
!
|
|
|
|
|
|
-
!
-
!
|
|
-
!
-
|
!
|
-
|
|
!
|
-
-
!
|
-
!
|
-
!
|
|
!
|
-
|
!
-
|
-
|
!
!
|
-
!
-
|
-
|
|
!
!
|
|
-
!
|
!
-
| class Character
{
private:
template<class TSelf, class TFuncObj>
struct Invoker
{
static void call(void* funcObj, void* self)
{
TFuncObj* func = static_cast<TFuncObj*>(funcObj);
(*func)(static_cast<TSelf*>(self));
}
static void dispose(void* funcObj)
{
delete static_cast<TFuncObj*>(funcObj);
}
};
void* _funcObj; void (*_invokeCall)(void*, void*); void (*_invokeDispose)(void*);
protected:
Character() : _funcObj(0), _invokeCall(0), _invokeDispose(0)
{
}
public:
virtual ~Character()
{
resetFunc();
}
template<class TSelf, class TFuncObj>
void setFunc(TFuncObj func)
{
resetFunc();
_funcObj = new TFuncObj(func);
typedef Invoker<TSelf, TFuncObj> Invoker;
_invokeCall = &Invoker::call;
_invokeDispose = &Invoker::dispose;
}
void doFunc()
{
if (_funcObj != 0)
{
_invokeCall(_funcObj, this);
}
}
void resetFunc()
{
if (_funcObj != 0)
{
_invokeDispose(_funcObj);
_funcObj = 0;
}
}
private:
Character(const Character&);
void operator=(const Character&);
};
|
まず注目すべきは49〜55行目です。
50行目では関数オブジェクトをコピーし、 void*
として保持しています。
この時点で _funcObj
からは引数 func
の持っていた型情報が消去されています。
これが型消去と呼ばれる理由です。
ではこの手法は型安全ではないのかというとそうではなく、型情報は54,55行目で保持している関数ポインタが持つことになります。
これらの関数は Character::Invoker<TSelf, TFuncObj>
テンプレートクラスの静的メンバ関数であり、関数そのものは非テンプレート関数と同じでありながらテンプレート型情報を持つことができます。
つまりこれらの関数によって型情報を復元することが可能であるというわけです。
それを踏まえて10〜14行目を見ると、関数に渡された引数を関数が持つ型情報でキャストして型復元していることがわかります。
この関数の呼び出しは64行目で行われており、第一引数には50行目で保持した関数オブジェクトが渡されているため、型が一致することは保証されます。
そのため、 void*
型からのキャストでありながら型安全が保証されているというカラクリが成立するわけです。
なお、この実装では setFunc
メンバ関数のテンプレート型引数 TSelf
に適当な型…例えば int
を渡すと、 this
を強引に int*
型にキャストして呼び出してしまいます。
これを防ぐためには TSelf
型が Character
クラスの派生クラスであるかコンパイル時にチェックする処理を入れるとよいでしょう。
例えばboostが使えるならば setFunc
メンバ関数の頭に次のコードを入れればいいはずです(もちろん必要なヘッダはインクルードしている前提で)。
| BOOST_STATIC_ASSERT(boost::is_base_of<Character, TSelf>::value);
|