Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

This is the documentation for an old version of boost. Click here for the latest Boost documentation.
Call Policies

In C++, we often deal with arguments and return types such as pointers and references. Such primitive types are rather, ummmm, low level and they really don't tell us much. At the very least, we don't know the owner of the pointer or the referenced object. No wonder languages such as Java and Python never deal with such low level entities. In C++, it's usually considered a good practice to use smart pointers which exactly describe ownership semantics. Still, even good C++ interfaces use raw references and pointers sometimes, so Boost.Python must deal with them. To do this, it may need your help. Consider the following C++ function:

    X& f(Y& y, Z* z);

How should the library wrap this function? A naive approach builds a Python X object around result reference. This strategy might or might not 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 lifetime of y, because the f() returns a reference to a member of the y object. This idiom is is not uncommon and perfectly acceptable in the context of C++. However, Python users should not be able to crash the system just by using our C++ interface. In this case deleting y will invalidate the reference to X. We have a dangling reference.

Here's what's happening:

  1. f is called passing in a reference to y and a pointer to z
  2. A reference to y.x is returned
  3. y is deleted. x is a dangling reference
  4. x.some_method() is called
  5. BOOM!

We could copy result into a new object:

    >>> f(y, z).set(42) ##Result disappears
    >>> y.x.get()       ##No crash, but still bad
    3.14

This is not really our intent of our C++ interface. We've broken our promise that the Python interface should reflect the C++ interface as closely as possible.

Our problems do not end there. Suppose Y is implemented as follows:

    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 raw pointer. 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:

  1. f is called passing in a reference to y and a pointer to z
  2. A pointer to z is held by y
  3. A reference to y.x is returned
  4. z is deleted. y.z is a dangling pointer
  5. y.z_value() is called
  6. z->value() is called
  7. BOOM!

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:

    def("f", f,
        return_internal_reference<1,
            with_custodian_and_ward<1, 2> >());

What are the 1 and 2 parameters, you ask?

    return_internal_reference<1

Informs Boost.Python that the first argument, in our case Y& y, is the owner of the returned reference: X&. The "1" simply specifies the first argument. In short: "return an internal reference X& owned by the 1st argument Y& y".

    with_custodian_and_ward<1, 2>

Informs Boost.Python that the lifetime of the argument indicated by ward (i.e. the 2nd argument: Z* z) is dependent on the lifetime of the argument indicated by custodian (i.e. the 1st argument: Y& y).

It is also important to note that we have defined two policies above. Two or more policies can be composed by chaining. Here's the general syntax:

    policy1<args...,
        policy2<args...,
            policy3<args...> > >

Here is the list of predefined call policies. A complete reference detailing these can be found here.

Remember the Zen, Luke:

"Explicit is better than implicit"
"In the face of ambiguity, refuse the temptation to guess"