読者です 読者をやめる 読者になる 読者になる

Boost.Functionの実装技術(1) - Type Erasure

Boost.Functionの実装方法はいくつかのサイトで紹介されているので(こことかここ)
私もBoost.Functionの実装技術について解説しよう


Boost.FunctionではType Erasure(型削除型消去)というテクニックが使用されている

実際にBoost.Functionを作りながら解説しよう


Boost.Functionは関数ポインタと関数オブジェクトを保持するようになっているので
今回は以下の使い方ができるように実装する

int func();
struct func_obj { int operator()(); };

// 関数ポインタ・関数オブジェクトの保持
function<int> f1 = func;
function<int> f2 = func_obj();

// 関数ポインタ・関数オブジェクトの実行
int result_func     = f1();
int result_func_obj = f2();

まず、関数ポインタと関数オブジェクトを持つ共用体を用意する
functionクラスはこれを使って関数ポインタ・関数オブジェクトを1つのインスタンス(1つのメモリ領域)に保持する

union any_pointer {
    void (*func_ptr)(); // 関数ポインタ
    void *obj_ptr;      // 関数オブジェクト
};

any_pointerにはtemplateは使われていない
型情報が削除された関数ポインタ・関数オブジェクトの実体だけが格納される



次に、関数ポインタと関数オブジェクトそれぞれの実行・削除・コピーを管理するクラスを作成する
これらのクラスはany_pointer内の関数ポインタと関数オブジェクトの本来の型を保持している

// 関数ポインタ
template <class FuncPtr, class R>
struct function_ptr_manager {
    static R invoke(any_pointer func_ptr)
    {
        FuncPtr func = reinterpret_cast<FuncPtr>(func_ptr.func_ptr); // void(*)()になってる関数ポインタを本来の型にキャスト
        return func();
    }

    // void destroy(any_pointer); ...
    // any_pointer clone(any_pointer); ...
};

// 関数オブジェクト
template <class FuncObj, class R>
struct function_obj_manager {
    static R invoke(any_pointer func_obj)
    {
        FuncObj* func = reinterpret_cast<FuncObj*>(func_obj.obj_ptr); // void*になってる関数オブジェクトを本来の型にキャスト
        return (*func)();
    }

    // void destroy(any_pointer); ...
    // any_pointer clone(any_pointer); ...
};

残りはfunctionクラスだ

// 戻り値の型だけを受け取る引数なしfunction
template <class R>
class function {
    R (*invoke_)(any_pointer); // 関数ポインタ・関数オブジェクトの実行関数

    any_pointer functor_; // 関数ポインタ・関数オブジェクト

public:
    // 関数ポインタの設定
    template <class FuncPtr>
    void set_function_ptr(FuncPtr func_ptr)
    {
        // 関数ポインタを実行する関数を登録
        invoke_ = &function_ptr_manager<FuncPtr, R>::invoke;

        // (型削除した)関数ポインタを保持
        functor_.func_ptr = reinterpret_cast<void(*)()>(func_ptr);
    }

    // 関数オブジェクトの設定
    template <class FuncObj>
    void set_function_obj(FuncObj func_obj)
    {
        // 関数オブジェクトを実行する関数を登録
        invoke_ = &function_obj_manager<FuncObj, R>::invoke;

        // (型削除した)関数オブジェクトを保持
        functor_.obj_ptr = reinterpret_cast<void*>(new FuncObj(func_obj));
    }

    // 関数ポインタ・関数オブジェクトの実行
    R operator()()
    {
        return invoke_(functor_);
    }
};

これで関数ポインタと関数オブジェクトを保持して実行する機能が付いたfunctionクラスができた
(関数ポインタ・関数オブジェクトの削除(function_xxx_manager::destroy)と
コピー(function_xxx_manager::clone)の実装はここでは説明しない)

int func() { return 1; }
struct func_obj { int operator()() { return 2; } };


function<int> f;

// 関数ポインタ
f.set_function_ptr(func); // 設定
int result = f();         // 実行

// 関数オブジェクト
f.set_function_obj(func_obj()); // 設定
result = f();                   // 実行


この段階ではまだ=演算子での関数ポインタ、関数オブジェクトの代入はできない
次回はその辺りを解説する