⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 bpl.txt

📁 boost库提供标准的C++ API 配合dev c++使用,功能更加强大
💻 TXT
📖 第 1 页 / 共 3 页
字号:
(DSLs), and that's what we've done in Boost.Python. To break it down::

    class_<World>("World")

constructs an unnamed object of type ``class_<World>`` and passes
``"World"`` to its constructor.  This creates a new-style Python class
called ``World`` in the extension module, and associates it with the
C++ type ``World`` in the Boost.Python type conversion registry.  We
might have also written::

    class_<World> w("World");

but that would've been more verbose, since we'd have to name ``w``
again to invoke its ``def()`` member function::

        w.def("greet", &World::greet)

There's nothing special about the location of the dot for member
access in the original example: C++ allows any amount of whitespace on
either side of a token, and placing the dot at the beginning of each
line allows us to chain as many successive calls to member functions
as we like with a uniform syntax.  The other key fact that allows
chaining is that ``class_<>`` member functions all return a reference
to ``*this``.

So the example is equivalent to::

    class_<World> w("World");
    w.def("greet", &World::greet);
    w.def("set", &World::set);

It's occasionally useful to be able to break down the components of a
Boost.Python class wrapper in this way, but the rest of this article
will stick to the terse syntax.

For completeness, here's the wrapped class in use: ::

    >>> import hello
    >>> planet = hello.World()
    >>> planet.set('howdy')
    >>> planet.greet()
    'howdy'

Constructors
============

Since our ``World`` class is just a plain ``struct``, it has an
implicit no-argument (nullary) constructor.  Boost.Python exposes the
nullary constructor by default, which is why we were able to write: ::

  >>> planet = hello.World()

However, well-designed classes in any language may require constructor
arguments in order to establish their invariants.  Unlike Python,
where ``__init__`` is just a specially-named method, In C++
constructors cannot be handled like ordinary member functions.  In
particular, we can't take their address: ``&World::World`` is an
error.  The library provides a different interface for specifying
constructors.  Given::

    struct World
    {
        World(std::string msg); // added constructor
        ...

we can modify our wrapping code as follows::

    class_<World>("World", init<std::string>())
        ...

of course, a C++ class may have additional constructors, and we can
expose those as well by passing more instances of ``init<...>`` to
``def()``::

    class_<World>("World", init<std::string>())
        .def(init<double, double>())
        ...

Boost.Python allows wrapped functions, member functions, and
constructors to be overloaded to mirror C++ overloading.

Data Members and Properties
===========================

Any publicly-accessible data members in a C++ class can be easily
exposed as either ``readonly`` or ``readwrite`` attributes::

    class_<World>("World", init<std::string>())
        .def_readonly("msg", &World::msg)
        ...

and can be used directly in Python: ::

    >>> planet = hello.World('howdy')
    >>> planet.msg
    'howdy'

This does *not* result in adding attributes to the ``World`` instance
``__dict__``, which can result in substantial memory savings when
wrapping large data structures.  In fact, no instance ``__dict__``
will be created at all unless attributes are explicitly added from
Python. Boost.Python owes this capability to the new Python 2.2 type
system, in particular the descriptor interface and ``property`` type.

In C++, publicly-accessible data members are considered a sign of poor
design because they break encapsulation, and style guides usually
dictate the use of "getter" and "setter" functions instead.  In
Python, however, ``__getattr__``, ``__setattr__``, and since 2.2,
``property`` mean that attribute access is just one more
well-encapsulated syntactic tool at the programmer's disposal.
Boost.Python bridges this idiomatic gap by making Python ``property``
creation directly available to users.  If ``msg`` were private, we
could still expose it as attribute in Python as follows::

    class_<World>("World", init<std::string>())
        .add_property("msg", &World::greet, &World::set)
        ...

The example above mirrors the familiar usage of properties in Python
2.2+: ::

    >>> class World(object):
    ...     __init__(self, msg):
    ...         self.__msg = msg
    ...     def greet(self):
    ...         return self.__msg
    ...     def set(self, msg):
    ...         self.__msg = msg
    ...     msg = property(greet, set)

Operator Overloading
====================

The ability to write arithmetic operators for user-defined types has
been a major factor in the success of both languages for numerical
computation, and the success of packages like NumPy_ attests to the
power of exposing operators in extension modules.  Boost.Python
provides a concise mechanism for wrapping operator overloads. The
example below shows a fragment from a wrapper for the Boost rational
number library::

    class_<rational<int> >("rational_int")
      .def(init<int, int>()) // constructor, e.g. rational_int(3,4)
      .def("numerator", &rational<int>::numerator)
      .def("denominator", &rational<int>::denominator)
      .def(-self)        // __neg__ (unary minus)
      .def(self + self)  // __add__ (homogeneous)
      .def(self * self)  // __mul__
      .def(self + int()) // __add__ (heterogenous)
      .def(int() + self) // __radd__
      ...

The magic is performed using a simplified application of "expression
templates" [VELD1995]_, a technique originally developed for
optimization of high-performance matrix algebra expressions.  The
essence is that instead of performing the computation immediately,
operators are overloaded to construct a type *representing* the
computation.  In matrix algebra, dramatic optimizations are often
available when the structure of an entire expression can be taken into
account, rather than evaluating each operation "greedily".
Boost.Python uses the same technique to build an appropriate Python
method object based on expressions involving ``self``.

.. _NumPy: http://www.pfdubois.com/numpy/

Inheritance
===========

C++ inheritance relationships can be represented to Boost.Python by adding
an optional ``bases<...>`` argument to the ``class_<...>`` template
parameter list as follows::

     class_<Derived, bases<Base1,Base2> >("Derived")
          ...

This has two effects:  

1. When the ``class_<...>`` is created, Python type objects
   corresponding to ``Base1`` and ``Base2`` are looked up in
   Boost.Python's registry, and are used as bases for the new Python
   ``Derived`` type object, so methods exposed for the Python ``Base1``
   and ``Base2`` types are automatically members of the ``Derived``
   type.  Because the registry is global, this works correctly even if
   ``Derived`` is exposed in a different module from either of its
   bases.

2. C++ conversions from ``Derived`` to its bases are added to the
   Boost.Python registry.  Thus wrapped C++ methods expecting (a
   pointer or reference to) an object of either base type can be
   called with an object wrapping a ``Derived`` instance.  Wrapped
   member functions of class ``T`` are treated as though they have an
   implicit first argument of ``T&``, so these conversions are
   neccessary to allow the base class methods to be called for derived
   objects.

Of course it's possible to derive new Python classes from wrapped C++
class instances.  Because Boost.Python uses the new-style class
system, that works very much as for the Python built-in types.  There
is one significant detail in which it differs: the built-in types
generally establish their invariants in their ``__new__`` function, so
that derived classes do not need to call ``__init__`` on the base
class before invoking its methods : ::

    >>> class L(list):
    ...      def __init__(self):
    ...          pass
    ...
    >>> L().reverse()
    >>> 

Because C++ object construction is a one-step operation, C++ instance
data cannot be constructed until the arguments are available, in the
``__init__`` function: ::

    >>> class D(SomeBoostPythonClass):
    ...      def __init__(self):
    ...          pass
    ...
    >>> D().some_boost_python_method()
    Traceback (most recent call last):
      File "<stdin>", line 1, in ?
    TypeError: bad argument type for built-in operation

This happened because Boost.Python couldn't find instance data of type
``SomeBoostPythonClass`` within the ``D`` instance; ``D``'s ``__init__``
function masked construction of the base class.  It could be corrected
by either removing ``D``'s ``__init__`` function or having it call
``SomeBoostPythonClass.__init__(...)`` explicitly.

Virtual Functions
=================

Deriving new types in Python from extension classes is not very
interesting unless they can be used polymorphically from C++.  In
other words, Python method implementations should appear to override
the implementation of C++ virtual functions when called *through base
class pointers/references from C++*.  Since the only way to alter the
behavior of a virtual function is to override it in a derived class,
the user must build a special derived class to dispatch a polymorphic
class' virtual functions::

    //
    // interface to wrap:
    //
    class Base
    {
     public:
        virtual int f(std::string x) { return 42; }
        virtual ~Base();
    };

    int calls_f(Base const& b, std::string x) { return b.f(x); }

    //
    // Wrapping Code
    //

    // Dispatcher class
    struct BaseWrap : Base
    {
        // Store a pointer to the Python object
        BaseWrap(PyObject* self_) : self(self_) {}
        PyObject* self;

        // Default implementation, for when f is not overridden
        int f_default(std::string x) { return this->Base::f(x); }
        // Dispatch implementation
        int f(std::string x) { return call_method<int>(self, "f", x); }
    };

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

Now here's some Python code which demonstrates: ::

    >>> class Derived(Base):
    ...     def f(self, s):
    ...          return len(s)
    ...
    >>> calls_f(Base(), 'foo')
    42
    >>> calls_f(Derived(), 'forty-two')
    9

Things to notice about the dispatcher class:

* The key element which allows overriding in Python is the
  ``call_method`` invocation, which uses the same global type
  conversion registry as the C++ function wrapping does to convert its
  arguments from C++ to Python and its return type from Python to C++.

* Any constructor signatures you wish to wrap must be replicated with
  an initial ``PyObject*`` argument

* The dispatcher must store this argument so that it can be used to
  invoke ``call_method``

* The ``f_default`` member function is needed when the function being
  exposed is not pure virtual; there's no other way ``Base::f`` can be
  called on an object of type ``BaseWrap``, since it overrides ``f``.

Deeper Reflection on the Horizon?
=================================

Admittedly, this formula is tedious to repeat, especially on a project
with many polymorphic classes.  That it is neccessary reflects some
limitations in C++'s compile-time introspection capabilities: there's
no way to enumerate the members of a class and find out which are
virtual functions.  At least one very promising project has been
started to write a front-end which can generate these dispatchers (and
other wrapping code) automatically from C++ headers.  

Pyste_ is being developed by Bruno da Silva de Oliveira.  It builds on

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -