Class Virtual Functions

このセクションでは、仮想関数を通して多態的にふるまう関数の作成方法を学ぶ。 次の例題は、Baseクラスに仮想関数を追加してある。

    struct Base
    {
        virtual int f() = 0;
    };

fは純粋仮想関数なので、Baseは抽象クラスである。 このクラスのあるインスタンスが与えられた場合、 自由な関数call_fは、実体を持つ派生クラスの中に存在する、 この仮想関数の実装を呼び出す。

    int call_f(Base& b) { return b.f(); }

この関数を、導出された Python のクラス内に実装するには、 クラスのラッパを作成する必要がある。

    struct BaseWrap : Base
    {
        BaseWrap(PyObject* self_)
            : self(self_) {}
        int f() { return call_method<int>(self, "f"); }
        PyObject* self;
    };
member function and methods

Python, like many object oriented languages uses the term methods. Methods correspond roughly to C++'s member functions

クラスラッパBaseWrapBaseから導出される。 仮想メンバ関数fは、 Python オブジェクトselfの対応メソッドが実際に呼ばれた時に上書きされる。 selfは Python オブジェクトBaseへの逆ポインタであり、 BaseWrapインスタンスが保持する。

Why do we need BaseWrap?

You may ask, "Why do we need the BaseWrap derived class? This could have been designed so that everything gets done right inside of Base."

One of the goals of Boost.Python is to be minimally intrusive on an existing C++ design. In principle, it should be possible to expose the interface for a 3rd party library without changing it. To unintrusively hook into the virtual functions so that a Python override may be called, we must use a derived class.

Note however that you don't need to do this to get methods overridden in Python to behave virtually when called from Python. The only time you need to do the BaseWrap dance is when you have a virtual function that's going to be overridden in Python and called polymorphically from C++.

Baseと自由な関数call_fのラッピング。

    class_<Base, BaseWrap, boost::noncopyable>("Base", no_init)
        ;
    def("call_f", call_f);

class_テンプレートはパラメタを持ち、 BaseWrapは二番目の引数になっていることに注意しなさい。 noncopyableとは何か? これがないと、ライブラリはBaseを変換する時に、 Baseがラップした関数の値を Python に返すようなコードを作成してしまう。 これをやるなら、Base のコピーコンストラクタが必要だ。 もちろん Base は抽象クラスなので、それは不可能だが。

Python でBaseクラスの初期化を試してみよう。

    >>> base = Base()
    AttributeError: ...

なぜエラーなのか? Base は抽象クラスである。 故に Python ラッパを定義する場合、上でおこなったように no_init を付けるのが賢明である。 そうすることで Base のような抽象基底クラスのインスタンス化を禁止する。

Deriving a Python class

最後に、基底クラス Base から Python に確かに導出できる。

    >>> class Derived(Base):
    ...     def f(self):
    ...         return 42
    ...

これね! C++ から導出した Python クラス。

さあ Python クラス Derived からインスタンスを生成しよう。

    >>> derived = Derived()

derived.f() 呼び出し。

    >>> derived.f()
    42

期待する結果を得るだろう。 最後に derived を引数として自由な関数 call_f を呼び出す。

    >>> call_f(derived)
    42

これもまた期待する結果を得るだろう。

起きていることは以下の通り。

  1. call_f(derived) が Python 上で呼ばれる。
  2. これは def("call_f", call_f); に対応している。 Boost.Python はこの呼び出しをディスパッチする。
  3. int call_f(Base& b) { return b.f(); } が呼び出しを受ける。
  4. BaseWrap でオーバライドされた仮想関数f が呼ばれる。
  5. call_method<int>(self, "f"); ディスパッチは Python に戻される。
  6. def f(self): return 42 が最後に呼び出される。

Base クラスを再考しよう。 仮にメンバ関数 f が純粋仮想関数として定義されていない場合、

    struct Base
    {
        virtual int f() { return 0; }
    };

そして上記のように、代わりに return 0 が実装されている場合。

    struct BaseWrap : Base
    {
        BaseWrap(PyObject* self_)
            : self(self_) {}
        int f() { return call_method<int>(self, "f"); }
        static int default_f(Base* b) { return b->Base::f(); } // <<=== added
        PyObject* self;
    };

その時の Boost.Python ラッパ。

    class_<Base, BaseWrap>("Base")
        .def("f", &BaseWrap::default_f)
        ;

今回は Base オブジェクトがインスタンス化されるのを許可していることに注意しなさい。 前回 class_<Base>no_init と供に明示的に定義したのとは異なる。

Python 上で結果は期待通りになるだろう。

    >>> base = Base()
    >>> class Derived(Base):
    ...     def f(self):
    ...         return 42
    ...
    >>> derived = Derived()

base.f() 呼び出し。

    >>> base.f()
    0

derived.f() 呼び出し。

    >>> derived.f()
    42

base オブジェクトを渡す call_f 呼び出し。

    >>> call_f(base)
    0

derived オブジェクトを渡す call_f 呼び出し。

    >>> call_f(derived)
    42