Tclのラッパーライブラリ(循環参照解決編)
高木です。こんばんは。
今回は連載中の「Tclのラッパーライブラリ」に関する記事なのですが、内容はほとんど一般論です。
一般論とはいえ、こういう話もしておくべきだろうということで書くことにします。
Tcl C APIの内容をながめていると、ラッパーライブラリを作る際にひっかかりそうなポイントが見えてくるものです。
クラス定義の循環参照が起きそうなところもそのひとつです。
今回は、そうしたクラス定義の循環参照が起きそうなところ、そしてその解決方法について書き留めることにします。
Tcl C APIの主要な関数だけを考えても、素直に設計すればクラス定義の循環参照は発生します。
何でもかんでも文字列として扱うレガシーなAPIしか使わないならいいのですが、Tcl_Obj型を用いた新しいAPIを使うのであれば、この問題は避けて通れません。
具体例を示しましょう。
Tcl言語のスクリプトを評価するTcl_EvalObjEx関数というのがあります。
この関数の宣言は次のようになっています。
1 | int Tcl_EvalObjEx(Tcl_Interp *interp, Tcl_Obj *objPtr, int flags); |
この関数をラップするなら、interpクラスのメンバ関数にするのが妥当かと思います。
しかし、第2引数はTcl_Obj型へのポインタですから、順当にいけばobjクラスの参照を受け取ることになるのでしょう。
一方で、Tcl_Obj型のオブジェクトから整数値を取り出すためのTcl_GetIntFromObj関数というのがあります。
この関数の宣言は次の通りです。
1 | int Tcl_GetIntFromObj(Tcl_Interp *interp, Tcl_Obj *objPtr, int *intPtr); |
この関数はobjクラスのメンバ関数としてラップするのが妥当でしょう。
こうなると、interpクラスを定義する前にobjクラスの定義が必要になりますし、objクラスを定義する前にinterpクラスの定義が必要になってきます。
まさに循環参照が起きています。
この問題を解決するには次のようにします。
該当部分だけを抜粋したサンプルを以下に示します(各メンバ関数は暫定です)。
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 | class interp; class obj; class interp { public: … int eval(obj const& script, int flags); … }; class obj { public: … bool get(interp& i, int* p) const; … }; inline int interp::eval(obj const& script, int flags) { return Tcl_EvalObjEx(this->handle_, obj.get(), flags); } inline bool obj::get(interp& i, int* p) const { return Tcl_GetIntFromObj(i.get(), this->handle_, p) != TCL_OK; } |
非常にザックリとしたサンプルですが、雰囲気はつかめるかと思います。
このようにメンバ関数の宣言と定義を分離する書き方は、最初にも書いたように、このライブラリ固有のものではなく、C++では割と一般的な技法です。
自分で最初から設計する場合は、極力このような循環が起きないようにしたほうがいいかもしれません。
しかし、今回のようにラッパーライブラリを作成する場合は、もともとの設計にどうしても強い影響を受けますので、致し方ないでしょう。