tutorial.qbk

来自「Boost provides free peer-reviewed portab」· QBK 代码 · 共 1,973 行 · 第 1/5 页

QBK
1,973
字号
    void b(Base*);    void d(Derived*);    Base* factory() { return new Derived; }We've seen how we can wrap the base class [^Base]:    class_<Base>("Base")        /*...*/        ;Now we can inform Boost.Python of the inheritance relationship between[^Derived] and its base class [^Base].  Thus:    class_<Derived, bases<Base> >("Derived")        /*...*/        ;Doing so, we get some things for free:# Derived automatically inherits all of Base's Python methods  (wrapped C++ member functions)# [*If] Base is polymorphic, [^Derived] objects which have been passed to  Python via a pointer or reference to [^Base] can be passed where a pointer  or reference to [^Derived] is expected.Now, we will expose the C++ free functions [^b] and [^d] and [^factory]:    def("b", b);    def("d", d);    def("factory", factory);Note that free function [^factory] is being used to generate newinstances of class [^Derived]. In such cases, we use[^return_value_policy<manage_new_object>] to instruct Python to adoptthe pointer to [^Base] and hold the instance in a new Python [^Base]object until the the Python object is destroyed. We will see more ofBoost.Python [link python.call_policies call policies] later.    // Tell Python to take ownership of factory's result    def("factory", factory,        return_value_policy<manage_new_object>());[endsect][section Class Virtual Functions]In this section, we will learn how to make functions behave polymorphicallythrough virtual functions. Continuing our example, let us add a virtual functionto our [^Base] class:    struct Base    {        virtual ~Base() {}        virtual int f() = 0;    };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 3rdparty library without changing it. It is not ideal to add anything to our class`Base`. Yet, when you have a virtual function that's going to be overridden inPython and called polymorphically *from C++*, we'll need to add somescaffoldings to make things work properly. What we'll do is write a classwrapper that derives from `Base` that will unintrusively hook into the virtualfunctions so that a Python override may be called:    struct BaseWrap : Base, wrapper<Base>    {        int f()        {            return this->get_override("f")();        }    };Notice too that in addition to inheriting from `Base`, we also multiply-inherited `wrapper<Base>` (See [@../../../v2/wrapper.html Wrapper]). The`wrapper` template makes the job of wrapping classes that are meant tooverridden in Python, easier.[blurb __alert__ [*MSVC6/7 Workaround]If you are using Microsoft Visual C++ 6 or 7, you have to write `f` as:`return call<int>(this->get_override("f").ptr());`.]BaseWrap's overridden virtual member function `f` in effect calls thecorresponding method of the Python object through `get_override`.Finally, exposing `Base`:    class_<BaseWrap, boost::noncopyable>("Base")        .def("f", pure_virtual(&Base::f))        ;`pure_virtual` signals Boost.Python that the function `f` is a pure virtualfunction.[note [*member function and methods]Python, like many object oriented languages uses the term [*methods].Methods correspond roughly to C++'s [*member functions]][endsect][section Virtual Functions with Default Implementations]We've seen in the previous section how classes with pure virtual functions arewrapped using Boost.Python's [@../../../v2/wrapper.html class wrapper]facilities. If we wish to wrap [*non]-pure-virtual functions instead, themechanism is a bit different.Recall that in the [link python.class_virtual_functions previous section], wewrapped a class with a pure virtual function that we then implemented in C++, orPython classes derived from it. Our base class:    struct Base    {        virtual int f() = 0;    };had a pure virtual function [^f]. If, however, its member function [^f] wasnot declared as pure virtual:    struct Base    {        virtual ~Base() {}        virtual int f() { return 0; }    };We wrap it this way:    struct BaseWrap : Base, wrapper<Base>    {        int f()        {            if (override f = this->get_override("f"))                return f(); // *note*            return Base::f();        }        int default_f() { return this->Base::f(); }    };Notice how we implemented `BaseWrap::f`. Now, we have to check if there is anoverride for `f`. If none, then we call `Base::f()`.[blurb __alert__ [*MSVC6/7 Workaround]If you are using Microsoft Visual C++ 6 or 7, you have to rewrite the linewith the `*note*` as:`return call<char const*>(f.ptr());`.]Finally, exposing:    class_<BaseWrap, boost::noncopyable>("Base")        .def("f", &Base::f, &BaseWrap::default_f)        ;Take note that we expose both `&Base::f` and `&BaseWrap::default_f`.Boost.Python needs to keep track of 1) the dispatch function [^f] and 2) theforwarding function to its default implementation [^default_f]. There's aspecial [^def] function for this purpose.In Python, the results would be as expected:[python]    >>> base = Base()    >>> class Derived(Base):    ...     def f(self):    ...         return 42    ...    >>> derived = Derived()Calling [^base.f()]:    >>> base.f()    0Calling [^derived.f()]:    >>> derived.f()    42[endsect][section Class Operators/Special Functions][h2 Python Operators]C is well known for the abundance of operators. C++ extends this to theextremes by allowing operator overloading. Boost.Python takes advantage ofthis and makes it easy to wrap C++ operator-powered classes.Consider a file position class [^FilePos] and a set of operators that takeon FilePos instances:[c++]    class FilePos { /*...*/ };    FilePos     operator+(FilePos, int);    FilePos     operator+(int, FilePos);    int         operator-(FilePos, FilePos);    FilePos     operator-(FilePos, int);    FilePos&    operator+=(FilePos&, int);    FilePos&    operator-=(FilePos&, int);    bool        operator<(FilePos, FilePos);The class and the various operators can be mapped to Python rather easilyand intuitively:    class_<FilePos>("FilePos")        .def(self + int())          // __add__        .def(int() + self)          // __radd__        .def(self - self)           // __sub__        .def(self - int())          // __sub__        .def(self += int())         // __iadd__        .def(self -= other<int>())        .def(self < self);          // __lt__The code snippet above is very clear and needs almost no explanation atall. It is virtually the same as the operators' signatures. Just takenote that [^self] refers to FilePos object. Also, not every class [^T] thatyou might need to interact with in an operator expression is (cheaply)default-constructible. You can use [^other<T>()] in place of an actual[^T] instance when writing "self expressions".[h2 Special Methods]Python has a few more ['Special Methods]. Boost.Python supports all of thestandard special method names supported by real Python class instances. Asimilar set of intuitive interfaces can also be used to wrap C++ functionsthat correspond to these Python ['special functions]. Example:    class Rational    { public: operator double() const; };    Rational pow(Rational, Rational);    Rational abs(Rational);    ostream& operator<<(ostream&,Rational);    class_<Rational>("Rational")        .def(float_(self))                  // __float__        .def(pow(self, other<Rational>))    // __pow__        .def(abs(self))                     // __abs__        .def(str(self))                     // __str__        ;Need we say more?[note What is the business of `operator<<`?Well, the method `str` requires the `operator<<` to do its work (i.e.`operator<<` is used by the method defined by `def(str(self))`.][endsect][endsect] [/ Exposing Classes ][section Functions]In this chapter, we'll look at Boost.Python powered functions in closerdetail. We will see some facilities to make exposing C++ functions toPython safe from potential pifalls such as dangling pointers andreferences. We will also see facilities that will make it even easier forus to expose C++ functions that take advantage of C++ features such asoverloading and default arguments.[:['Read on...]]But before you do, you might want to fire up Python 2.2 or later and type[^>>> import this].[pre>>> import thisThe Zen of Python, by Tim PetersBeautiful is better than ugly.Explicit is better than implicit.Simple is better than complex.Complex is better than complicated.Flat is better than nested.Sparse is better than dense.Readability counts.Special cases aren't special enough to break the rules.Although practicality beats purity.Errors should never pass silently.Unless explicitly silenced.In the face of ambiguity, refuse the temptation to guess.There should be one-- and preferably only one --obvious way to do itAlthough that way may not be obvious at first unless you're Dutch.Now is better than never.Although never is often better than *right* now.If the implementation is hard to explain, it's a bad idea.If the implementation is easy to explain, it may be a good idea.Namespaces are one honking great idea -- let's do more of those!][section Call Policies]In C++, we often deal with arguments and return types such as pointersand references. Such primitive types are rather, ummmm, low level andthey really don't tell us much. At the very least, we don't know theowner of the pointer or the referenced object. No wonder languagessuch as Java and Python never deal with such low level entities. InC++, it's usually considered a good practice to use smart pointerswhich exactly describe ownership semantics. Still, even good C++interfaces use raw references and pointers sometimes, so Boost.Pythonmust deal with them. To do this, it may need your help. Consider thefollowing C++ function:    X& f(Y& y, Z* z);How should the library wrap this function? A naive approach builds aPython X object around result reference. This strategy might or mightnot work out. Here's an example where it didn't    >>> x = f(y, z) # x refers to some C++ X    >>> del y    >>> x.some_method() # CRASH!What's the problem?Well, what if f() was implemented as shown below:    X& f(Y& y, Z* z)    {        y.z = z;        return y.x;    }The problem is that the lifetime of result X& is tied to the lifetimeof y, because the f() returns a reference to a member of the yobject. This idiom is is not uncommon and perfectly acceptable in thecontext of C++. However, Python users should not be able to crash thesystem just by using our C++ interface. In this case deleting y willinvalidate the reference to X. We have a dangling reference.Here's what's happening:# [^f] is called passing in a reference to [^y] and a pointer to [^z]# A reference to [^y.x] is returned# [^y] is deleted. [^x] is a dangling reference# [^x.some_method()] is called# [*BOOM!]We could copy result into a new object:[python]    >>> f(y, z).set(42) # Result disappears    >>> y.x.get()       # No crash, but still bad    3.14This is not really our intent of our C++ interface. We've broken ourpromise that the Python interface should reflect the C++ interface asclosely as possible.Our problems do not end there. Suppose Y is implemented as follows:[c++]    struct Y    {        X x; Z* z;        int z_value() { return z->value(); }    };Notice that the data member [^z] is held by class Y using a rawpointer. Now we have a potential dangling pointer problem inside Y:    >>> x = f(y, z) # y refers to z    >>> del z       # Kill the z object    >>> y.z_value() # CRASH!For reference, here's the implementation of [^f] again:    X& f(Y& y, Z* z)    {        y.z = z;        return y.x;    }Here's what's happening:# [^f] is called passing in a reference to [^y] and a pointer to [^z]# A pointer to [^z] is held by [^y]# A reference to [^y.x] is returned# [^z] is deleted. [^y.z] is a dangling pointer# [^y.z_value()] is called# [^z->value()] is called# [*BOOM!][h2 Call Policies]Call Policies may be used in situations such as the example detailed above.In our example, [^return_internal_reference] and [^with_custodian_and_ward]are our friends:

⌨️ 快捷键说明

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