1 | .. Copyright David Abrahams 2006. Distributed under the Boost |
---|
2 | .. Software License, Version 1.0. (See accompanying |
---|
3 | .. file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) |
---|
4 | |
---|
5 | Here's the plan: |
---|
6 | |
---|
7 | I aim to provide an interface similar to that of Boost.Python v1's |
---|
8 | callback<>::call(...) for dealing with callbacks. The interface will |
---|
9 | look like: |
---|
10 | |
---|
11 | returning<ResultType>::call("method_name", self_object, a1, a2...); |
---|
12 | |
---|
13 | or |
---|
14 | |
---|
15 | returning<ResultType>::call(callable_object, a1, a2...); |
---|
16 | |
---|
17 | ARGUMENT HANDLING |
---|
18 | |
---|
19 | There is an issue concerning how to make Python objects from the |
---|
20 | arguments a1...aN. A new Python object must be created; should the C++ |
---|
21 | object be copied into that Python object, or should the Python object |
---|
22 | simply hold a reference/pointer to the C++ object? In general, the |
---|
23 | latter approach is unsafe, since the called function may store a |
---|
24 | reference to the Python object somewhere. If the Python object is used |
---|
25 | after the C++ object is destroyed, we'll crash Python. |
---|
26 | |
---|
27 | I plan to make the copying behavior the default, and to allow a |
---|
28 | non-copying behavior if the user writes boost::ref(a1) instead of a1 |
---|
29 | directly. At least this way, the user doesn't get dangerous behavior "by |
---|
30 | accident". It's also worth noting that the non-copying ("by-reference") |
---|
31 | behavior is in general only available for class types, and will fail at |
---|
32 | runtime with a Python exception if used otherwise** |
---|
33 | |
---|
34 | However, pointer types present a problem: My first thought is to refuse |
---|
35 | to compile if any aN has pointer type: after all, a user can always pass |
---|
36 | *aN to pass "by-value" or ref(*aN) to indicate a pass-by-reference |
---|
37 | behavior. However, this creates a problem for the expected NULL pointer |
---|
38 | => None conversion: it's illegal to dereference a null pointer value. |
---|
39 | |
---|
40 | We could use another construct, say "ptr(aN)", to deal with null |
---|
41 | pointers, but then what does it mean? We know what it does when aN is |
---|
42 | NULL, but it might either have by-value or by-reference behavior when aN |
---|
43 | is non-null. |
---|
44 | |
---|
45 | The compromise I've settled on is this: |
---|
46 | |
---|
47 | 1. The default behavior is pass-by-value. If you pass a non-null |
---|
48 | pointer, the pointee is copied into a new Python object; otherwise |
---|
49 | the corresponding Python argument will be None. |
---|
50 | |
---|
51 | 2. if you want by-reference behavior, use ptr(aN) if aN is a pointer |
---|
52 | and ref(aN) otherwise. If a null pointer is passed to ptr(aN), the |
---|
53 | corresponding Python argument will be None. |
---|
54 | |
---|
55 | RESULT HANDLING |
---|
56 | |
---|
57 | As for results, we have a similar problem: if ResultType is allowed to |
---|
58 | be a pointer or reference type, the lifetime of the object it refers to |
---|
59 | is probably being managed by a Python object. When that Python object is |
---|
60 | destroyed, our pointer dangles. The problem is particularly bad when the |
---|
61 | ResultType is char const* - the corresponding Python String object is |
---|
62 | typically uniquely-referenced, meaning that the pointer dangles as soon |
---|
63 | as returning<char const*>::call() returns. |
---|
64 | |
---|
65 | Boost.Python v1 deals with this issue by refusing to compile any uses of |
---|
66 | callback<char const*>::call(), but IMO this goes both too far and not |
---|
67 | far enough. It goes too far because there are cases where the owning |
---|
68 | String object survives beyond the call (just for instance when it's the |
---|
69 | name of a Python class), and it goes not far enough because we might |
---|
70 | just as well have the same problem with any returned pointer or |
---|
71 | reference. |
---|
72 | |
---|
73 | I propose to address this in Boost.Python v2 by |
---|
74 | |
---|
75 | 1. lifting the compile-time restriction on const |
---|
76 | char* callback returns |
---|
77 | |
---|
78 | 2. detecting the case when the reference count on the |
---|
79 | result Python object is 1 and throwing an exception |
---|
80 | inside of returning<U>::call() when U is a pointer or |
---|
81 | reference type. |
---|
82 | |
---|
83 | I think this is acceptably safe because users have to explicitly specify |
---|
84 | a pointer/reference for U in returning<U>, and they will be protected |
---|
85 | against dangles at runtime, at least long enough to get out of the |
---|
86 | returning<U>::call() invocation. |
---|
87 | |
---|
88 | -Dave |
---|
89 | |
---|
90 | **It would be possible to make it fail at compile-time for non-class |
---|
91 | types such as int and char, but I'm not sure it's a good idea to impose |
---|
92 | this restriction yet. |
---|