forked from pybind/pybind11
-
Notifications
You must be signed in to change notification settings - Fork 0
/
test_unique_ptr_cast.cc
363 lines (320 loc) · 8.96 KB
/
test_unique_ptr_cast.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
// Purpose: Test what avenues might be possible for creating instances in Python
// to then be owned in C++.
#include <cstddef>
#include <cmath>
#include <sstream>
#include <string>
#include <pybind11/cast.h>
#include <pybind11/embed.h>
#include <pybind11/eval.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
namespace py = pybind11;
using namespace py::literals;
using namespace std;
class SimpleType {
public:
SimpleType(int value)
: value_(value) {
cout << "SimpleType::SimpleType()" << endl;
}
~SimpleType() {
cout << "SimpleType::~SimpleType()" << endl;
}
int value() const { return value_; }
private:
int value_{};
};
class Base {
public:
Base(int value)
: value_(value) {
cout << "Base::Base(int)\n";
}
virtual ~Base() {
cout << "Base::~Base()\n";
}
virtual int value() const {
cout << "Base::value()\n";
return value_;
}
private:
int value_{};
};
class Child : public Base {
public:
Child(int value)
: Base(value) {}
~Child() {
cout << "Child::~Child()\n";
}
int value() const override {
cout << "Child::value()\n";
return 10 * Base::value();
}
};
class ChildB : public Base {
public:
ChildB(int value)
: Base(value) {}
~ChildB() {
cout << "ChildB::~ChildB()\n";
}
int value() const override {
cout << "ChildB::value()\n";
return 10 * Base::value();
}
};
// TODO(eric.cousineau): Add converter for `is_base<T, trampoline<T>>`, only for
// `cast` (C++ to Python) to handle swapping lifetime control.
// Trampoline class.
class PyBase : public py::trampoline<Base> {
public:
typedef py::trampoline<Base> TBase;
using TBase::TBase;
~PyBase() {
cout << "PyBase::~PyBase()" << endl;
}
int value() const override {
PYBIND11_OVERLOAD(int, Base, value);
}
};
class PyChild : public py::trampoline<Child> {
public:
typedef py::trampoline<Child> Base;
using Base::Base;
~PyChild() {
cout << "PyChild::~PyChild()" << endl;
}
int value() const override {
PYBIND11_OVERLOAD(int, Child, value);
}
};
class PyChildB : public py::trampoline<ChildB> {
public:
typedef py::trampoline<ChildB> Base;
using Base::Base;
~PyChildB() {
cout << "PyChildB::~PyChildB()" << endl;
}
int value() const override {
PYBIND11_OVERLOAD(int, ChildB, value);
}
};
unique_ptr<Base> check_creation(py::function create_obj) {
// Test getting a pointer.
// Base* in_test = py::cast<Base*>(obj);
// Base a terminal pointer.
// NOTE: This yields a different destructor order.
// However, the trampoline class destructors should NOT interfere with nominal
// Python destruction.
cout << "---\n";
unique_ptr<Base> fin = py::cast<unique_ptr<Base>>(create_obj());
fin.reset();
cout << "---\n";
// Test pass-through.
py::object obj = create_obj();
unique_ptr<Base> in = py::cast<unique_ptr<Base>>(std::move(obj));
return in;
}
unique_ptr<SimpleType> check_creation_simple(py::function create_obj) {
cout << "---\n";
unique_ptr<SimpleType> fin = py::cast<unique_ptr<SimpleType>>(create_obj());
fin.reset();
cout << "---\n";
py::object obj = create_obj();
unique_ptr<SimpleType> in = py::cast<unique_ptr<SimpleType>>(std::move(obj));
return in;
}
// TODO(eric.cousineau): If a user uses `object` as a pass in, it should keep the reference count low
// (so that we can steal it, if need be).
// Presently, `pybind11` increases that reference count if `object` is an argument.
// Check casting.
unique_ptr<Base> check_cast_pass_thru(unique_ptr<Base> in) { //py::handle h) { //
// py::object py_in = py::reinterpret_steal<py::object>(h);
// auto in = py::cast<unique_ptr<Base>>(std::move(py_in));
cout << "Pass through: " << in->value()<< endl;
return in;
}
unique_ptr<Base> check_clone(unique_ptr<Base> in) {
// auto in = py::cast<unique_ptr<Base>>(std::move(py_in));
cout << "Clone: " << in->value()<< endl;
unique_ptr<Base> out(new Base(20 * in->value()));
return out;
}
unique_ptr<Base> check_new() {
return make_unique<Base>(10);
}
void terminal_func(unique_ptr<Base> ptr) {
cout << "Value: " << ptr->value() << endl;
ptr.reset(); // This will destroy the instance.
cout << "Destroyed in C++" << endl;
}
PYBIND11_MODULE(_move, m) {
py::class_<Base, PyBase>(m, "Base")
.def(py::init<int>())
.def("value", &Base::value);
py::class_<Child, PyChild, Base>(m, "Child")
.def(py::init<int>())
.def("value", &Child::value);
// NOTE: Not explicit calling `Base` as a base. Relying on Python downcasting via `py_type`.
py::class_<ChildB, PyChildB>(m, "ChildB")
.def(py::init<int>())
.def("value", &ChildB::value);
m.def("check_creation", &check_creation);
m.def("check_cast_pass_thru", &check_cast_pass_thru);
m.def("check_clone", &check_clone);
m.def("check_new", &check_new);
m.def("terminal_func", &terminal_func);
// Make sure this setup doesn't botch the usage of `shared_ptr`, compile or run-time.
class SharedClass {};
py::class_<SharedClass, shared_ptr<SharedClass>>(m, "SharedClass");
// Make sure this also still works with non-virtual, non-trampoline types.
py::class_<SimpleType>(m, "SimpleType")
.def(py::init<int>())
.def("value", &SimpleType::value);
m.def("check_creation_simple", &check_creation_simple);
auto mdict = m.attr("__dict__");
py::exec(R"(
class PyExtBase(Base):
def __init__(self, value):
Base.__init__(self, value)
print("PyExtBase.PyExtBase")
def __del__(self):
print("PyExtBase.__del__")
def value(self):
print("PyExtBase.value")
return 10 * Base.value(self)
class PyExtChild(Child):
def __init__(self, value):
Child.__init__(self, value)
print("PyExtChild.PyExtChild")
def __del__(self):
print("PyExtChild.__del__")
def value(self):
print("PyExtChild.value")
return Child.value(self)
class PyExtChildB(ChildB):
def __init__(self, value):
ChildB.__init__(self, value)
print("PyExtChildB.PyExtChildB")
def __del__(self):
print("PyExtChildB.__del__")
def value(self):
print("PyExtChildB.value")
return ChildB.value(self)
)", mdict, mdict);
// Define move container thing
py::exec(R"(
class PyMove:
""" Provide a wrapper to permit passing an object to be owned by C++ """
_is_move_container = True
def __init__(self, obj):
assert obj is not None
self._obj = obj
def release(self):
from sys import getrefcount
obj = self._obj
self._obj = None
ref_count = getrefcount(obj)
# Cannot use `assert ...`, because it will leave a latent reference?
# Consider a `with` reference?
if ref_count > 2:
obj = None
raise AssertionError("Object reference is not unique, got {} extra references".format(ref_count - 2))
else:
assert ref_count == 2
return obj
)", py::globals(), mdict);
}
// Export this to get access as we desire.
void custom_init_move(py::module& m) {
PYBIND11_CONCAT(pybind11_init_, _move)(m);
}
void check_pure_cpp_simple() {
cout << "\n[ check_pure_cpp_simple ]\n";
py::exec(R"(
def create_obj():
return [m.SimpleType(256)]
obj = m.check_creation_simple(create_obj)
print(obj.value())
del obj # Calling `del` since scoping isn't as tight here???
)");
}
void check_pure_cpp() {
cout << "\n[ check_pure_cpp ]\n";
py::exec(R"(
def create_obj():
return [m.Base(10)]
obj = m.check_creation(create_obj)
print(obj.value())
del obj
)");
}
void check_pass_thru() {
cout << "\n[ check_pure_cpp ]\n";
py::exec(R"(
obj = m.check_cast_pass_thru([m.Base(20)])
print(obj.value())
del obj
obj = m.check_clone([m.Base(30)])
print(obj.value())
del obj
)");
}
void check_py_child() {
// Check ownership for a Python-extended C++ class.
cout << "\n[ check_py_child ]\n";
py::exec(R"(
def create_obj():
return [m.PyExtBase(20)]
obj = m.check_creation(create_obj)
print(obj.value())
del obj
)");
}
void check_casting() {
// Check a class which, in C++, derives from the direct type, but not the alias.
cout << "\n[ check_casting ]\n";
py::exec(R"(
def create_obj():
return [m.PyExtChild(30)]
obj = m.check_creation(create_obj)
print(obj.value())
del obj
)");
}
void check_casting_without_explicit_base() {
// Check a class which, in C++, derives from the direct type, but not the alias.
cout << "\n[ check_casting_without_explicit_base ]\n";
py::exec(R"(
def create_obj():
return [m.PyExtChildB(30)]
obj = m.check_creation(create_obj)
print(obj.value())
del obj
)");
}
void check_terminal() {
cout << "\n[ check_terminal ]\n";
py::exec(R"(
m.terminal_func([m.PyExtBase(20)])
)");
}
int main() {
{
py::scoped_interpreter guard{};
py::module m("_move");
custom_init_move(m);
py::globals()["m"] = m;
check_pass_thru();
check_pure_cpp_simple();
check_pure_cpp();
check_py_child();
check_casting();
check_casting_without_explicit_base();
check_terminal();
}
cout << "[ Done ]" << endl;
return 0;
}