test_factory_constructors.cpp 16.2 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
/*
    tests/test_factory_constructors.cpp -- tests construction from a factory function
                                           via py::init_factory()

    Copyright (c) 2017 Jason Rhinelander <jason@imaginary.ca>

    All rights reserved. Use of this source code is governed by a
    BSD-style license that can be found in the LICENSE file.
*/

#include "pybind11_tests.h"
#include "constructor_stats.h"
#include <cmath>
14
#include <new>
15
16

// Classes for testing python construction via C++ factory function:
Unknown's avatar
Unknown committed
17
// Not publicly constructible, copyable, or movable:
18
19
20
21
22
class TestFactory1 {
    friend class TestFactoryHelper;
    TestFactory1() : value("(empty)") { print_default_created(this); }
    TestFactory1(int v) : value(std::to_string(v)) { print_created(this, value); }
    TestFactory1(std::string v) : value(std::move(v)) { print_created(this, value); }
23
24
public:
    std::string value;
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
    TestFactory1(TestFactory1 &&) = delete;
    TestFactory1(const TestFactory1 &) = delete;
    TestFactory1 &operator=(TestFactory1 &&) = delete;
    TestFactory1 &operator=(const TestFactory1 &) = delete;
    ~TestFactory1() { print_destroyed(this); }
};
// Non-public construction, but moveable:
class TestFactory2 {
    friend class TestFactoryHelper;
    TestFactory2() : value("(empty2)") { print_default_created(this); }
    TestFactory2(int v) : value(std::to_string(v)) { print_created(this, value); }
    TestFactory2(std::string v) : value(std::move(v)) { print_created(this, value); }
public:
    TestFactory2(TestFactory2 &&m) { value = std::move(m.value); print_move_created(this); }
    TestFactory2 &operator=(TestFactory2 &&m) { value = std::move(m.value); print_move_assigned(this); return *this; }
    std::string value;
    ~TestFactory2() { print_destroyed(this); }
};
// Mixed direct/factory construction:
class TestFactory3 {
protected:
    friend class TestFactoryHelper;
    TestFactory3() : value("(empty3)") { print_default_created(this); }
    TestFactory3(int v) : value(std::to_string(v)) { print_created(this, value); }
public:
    TestFactory3(std::string v) : value(std::move(v)) { print_created(this, value); }
    TestFactory3(TestFactory3 &&m) { value = std::move(m.value); print_move_created(this); }
    TestFactory3 &operator=(TestFactory3 &&m) { value = std::move(m.value); print_move_assigned(this); return *this; }
    std::string value;
    virtual ~TestFactory3() { print_destroyed(this); }
};
// Inheritance test
class TestFactory4 : public TestFactory3 {
public:
    TestFactory4() : TestFactory3() { print_default_created(this); }
    TestFactory4(int v) : TestFactory3(v) { print_created(this, v); }
61
    ~TestFactory4() override { print_destroyed(this); }
62
63
64
65
66
};
// Another class for an invalid downcast test
class TestFactory5 : public TestFactory3 {
public:
    TestFactory5(int i) : TestFactory3(i) { print_created(this, i); }
67
    ~TestFactory5() override { print_destroyed(this); }
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
};

class TestFactory6 {
protected:
    int value;
    bool alias = false;
public:
    TestFactory6(int i) : value{i} { print_created(this, i); }
    TestFactory6(TestFactory6 &&f) { print_move_created(this); value = f.value; alias = f.alias; }
    TestFactory6(const TestFactory6 &f) { print_copy_created(this); value = f.value; alias = f.alias; }
    virtual ~TestFactory6() { print_destroyed(this); }
    virtual int get() { return value; }
    bool has_alias() { return alias; }
};
class PyTF6 : public TestFactory6 {
public:
    // Special constructor that allows the factory to construct a PyTF6 from a TestFactory6 only
    // when an alias is needed:
    PyTF6(TestFactory6 &&base) : TestFactory6(std::move(base)) { alias = true; print_created(this, "move", value); }
    PyTF6(int i) : TestFactory6(i) { alias = true; print_created(this, i); }
    PyTF6(PyTF6 &&f) : TestFactory6(std::move(f)) { print_move_created(this); }
    PyTF6(const PyTF6 &f) : TestFactory6(f) { print_copy_created(this); }
    PyTF6(std::string s) : TestFactory6((int) s.size()) { alias = true; print_created(this, s); }
91
    ~PyTF6() override { print_destroyed(this); }
92
    int get() override { PYBIND11_OVERRIDE(int, TestFactory6, get, /*no args*/); }
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
};

class TestFactory7 {
protected:
    int value;
    bool alias = false;
public:
    TestFactory7(int i) : value{i} { print_created(this, i); }
    TestFactory7(TestFactory7 &&f) { print_move_created(this); value = f.value; alias = f.alias; }
    TestFactory7(const TestFactory7 &f) { print_copy_created(this); value = f.value; alias = f.alias; }
    virtual ~TestFactory7() { print_destroyed(this); }
    virtual int get() { return value; }
    bool has_alias() { return alias; }
};
class PyTF7 : public TestFactory7 {
public:
    PyTF7(int i) : TestFactory7(i) { alias = true; print_created(this, i); }
    PyTF7(PyTF7 &&f) : TestFactory7(std::move(f)) { print_move_created(this); }
    PyTF7(const PyTF7 &f) : TestFactory7(f) { print_copy_created(this); }
112
    ~PyTF7() override { print_destroyed(this); }
113
    int get() override { PYBIND11_OVERRIDE(int, TestFactory7, get, /*no args*/); }
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
};


class TestFactoryHelper {
public:
    // Non-movable, non-copyable type:
    // Return via pointer:
    static TestFactory1 *construct1() { return new TestFactory1(); }
    // Holder:
    static std::unique_ptr<TestFactory1> construct1(int a) { return std::unique_ptr<TestFactory1>(new TestFactory1(a)); }
    // pointer again
    static TestFactory1 *construct1_string(std::string a) { return new TestFactory1(a); }

    // Moveable type:
    // pointer:
    static TestFactory2 *construct2() { return new TestFactory2(); }
    // holder:
    static std::unique_ptr<TestFactory2> construct2(int a) { return std::unique_ptr<TestFactory2>(new TestFactory2(a)); }
    // by value moving:
    static TestFactory2 construct2(std::string a) { return TestFactory2(a); }

    // shared_ptr holder type:
    // pointer:
    static TestFactory3 *construct3() { return new TestFactory3(); }
    // holder:
    static std::shared_ptr<TestFactory3> construct3(int a) { return std::shared_ptr<TestFactory3>(new TestFactory3(a)); }
};

TEST_SUBMODULE(factory_constructors, m) {

    // Define various trivial types to allow simpler overload resolution:
145
    py::module_ m_tag = m.def_submodule("tag");
146
147
148
149
150
151
152
153
154
155
156
157
#define MAKE_TAG_TYPE(Name) \
    struct Name##_tag {}; \
    py::class_<Name##_tag>(m_tag, #Name "_tag").def(py::init<>()); \
    m_tag.attr(#Name) = py::cast(Name##_tag{})
    MAKE_TAG_TYPE(pointer);
    MAKE_TAG_TYPE(unique_ptr);
    MAKE_TAG_TYPE(move);
    MAKE_TAG_TYPE(shared_ptr);
    MAKE_TAG_TYPE(derived);
    MAKE_TAG_TYPE(TF4);
    MAKE_TAG_TYPE(TF5);
    MAKE_TAG_TYPE(null_ptr);
158
159
    MAKE_TAG_TYPE(null_unique_ptr);
    MAKE_TAG_TYPE(null_shared_ptr);
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
    MAKE_TAG_TYPE(base);
    MAKE_TAG_TYPE(invalid_base);
    MAKE_TAG_TYPE(alias);
    MAKE_TAG_TYPE(unaliasable);
    MAKE_TAG_TYPE(mixed);

    // test_init_factory_basic, test_bad_type
    py::class_<TestFactory1>(m, "TestFactory1")
        .def(py::init([](unique_ptr_tag, int v) { return TestFactoryHelper::construct1(v); }))
        .def(py::init(&TestFactoryHelper::construct1_string)) // raw function pointer
        .def(py::init([](pointer_tag) { return TestFactoryHelper::construct1(); }))
        .def(py::init([](py::handle, int v, py::handle) { return TestFactoryHelper::construct1(v); }))
        .def_readwrite("value", &TestFactory1::value)
        ;
    py::class_<TestFactory2>(m, "TestFactory2")
        .def(py::init([](pointer_tag, int v) { return TestFactoryHelper::construct2(v); }))
        .def(py::init([](unique_ptr_tag, std::string v) { return TestFactoryHelper::construct2(v); }))
        .def(py::init([](move_tag) { return TestFactoryHelper::construct2(); }))
        .def_readwrite("value", &TestFactory2::value)
        ;

    // Stateful & reused:
    int c = 1;
Jason Rhinelander's avatar
Jason Rhinelander committed
183
    auto c4a = [c](pointer_tag, TF4_tag, int a) { (void) c; return new TestFactory4(a);};
184
185

    // test_init_factory_basic, test_init_factory_casting
186
187
    py::class_<TestFactory3, std::shared_ptr<TestFactory3>> pyTestFactory3(m, "TestFactory3");
    pyTestFactory3
188
        .def(py::init([](pointer_tag, int v) { return TestFactoryHelper::construct3(v); }))
189
190
191
192
193
        .def(py::init([](shared_ptr_tag) { return TestFactoryHelper::construct3(); }));
    ignoreOldStyleInitWarnings([&pyTestFactory3]() {
        pyTestFactory3.def("__init__", [](TestFactory3 &self, std::string v) { new (&self) TestFactory3(v); }); // placement-new ctor
    });
    pyTestFactory3
194
195
196
197
198
199
200
201
202
        // factories returning a derived type:
        .def(py::init(c4a)) // derived ptr
        .def(py::init([](pointer_tag, TF5_tag, int a) { return new TestFactory5(a); }))
        // derived shared ptr:
        .def(py::init([](shared_ptr_tag, TF4_tag, int a) { return std::make_shared<TestFactory4>(a); }))
        .def(py::init([](shared_ptr_tag, TF5_tag, int a) { return std::make_shared<TestFactory5>(a); }))

        // Returns nullptr:
        .def(py::init([](null_ptr_tag) { return (TestFactory3 *) nullptr; }))
203
204
        .def(py::init([](null_unique_ptr_tag) { return std::unique_ptr<TestFactory3>(); }))
        .def(py::init([](null_shared_ptr_tag) { return std::shared_ptr<TestFactory3>(); }))
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

        .def_readwrite("value", &TestFactory3::value)
        ;

    // test_init_factory_casting
    py::class_<TestFactory4, TestFactory3, std::shared_ptr<TestFactory4>>(m, "TestFactory4")
        .def(py::init(c4a)) // pointer
        ;

    // Doesn't need to be registered, but registering makes getting ConstructorStats easier:
    py::class_<TestFactory5, TestFactory3, std::shared_ptr<TestFactory5>>(m, "TestFactory5");

    // test_init_factory_alias
    // Alias testing
    py::class_<TestFactory6, PyTF6>(m, "TestFactory6")
        .def(py::init([](base_tag, int i) { return TestFactory6(i); }))
        .def(py::init([](alias_tag, int i) { return PyTF6(i); }))
        .def(py::init([](alias_tag, std::string s) { return PyTF6(s); }))
        .def(py::init([](alias_tag, pointer_tag, int i) { return new PyTF6(i); }))
        .def(py::init([](base_tag, pointer_tag, int i) { return new TestFactory6(i); }))
        .def(py::init([](base_tag, alias_tag, pointer_tag, int i) { return (TestFactory6 *) new PyTF6(i); }))

        .def("get", &TestFactory6::get)
        .def("has_alias", &TestFactory6::has_alias)

        .def_static("get_cstats", &ConstructorStats::get<TestFactory6>, py::return_value_policy::reference)
        .def_static("get_alias_cstats", &ConstructorStats::get<PyTF6>, py::return_value_policy::reference)
        ;

    // test_init_factory_dual
    // Separate alias constructor testing
    py::class_<TestFactory7, PyTF7, std::shared_ptr<TestFactory7>>(m, "TestFactory7")
        .def(py::init(
            [](int i) { return TestFactory7(i); },
            [](int i) { return PyTF7(i); }))
        .def(py::init(
            [](pointer_tag, int i) { return new TestFactory7(i); },
            [](pointer_tag, int i) { return new PyTF7(i); }))
        .def(py::init(
            [](mixed_tag, int i) { return new TestFactory7(i); },
            [](mixed_tag, int i) { return PyTF7(i); }))
        .def(py::init(
            [](mixed_tag, std::string s) { return TestFactory7((int) s.size()); },
            [](mixed_tag, std::string s) { return new PyTF7((int) s.size()); }))
        .def(py::init(
            [](base_tag, pointer_tag, int i) { return new TestFactory7(i); },
            [](base_tag, pointer_tag, int i) { return (TestFactory7 *) new PyTF7(i); }))
        .def(py::init(
            [](alias_tag, pointer_tag, int i) { return new PyTF7(i); },
            [](alias_tag, pointer_tag, int i) { return new PyTF7(10*i); }))
        .def(py::init(
            [](shared_ptr_tag, base_tag, int i) { return std::make_shared<TestFactory7>(i); },
            [](shared_ptr_tag, base_tag, int i) { auto *p = new PyTF7(i); return std::shared_ptr<TestFactory7>(p); }))
        .def(py::init(
            [](shared_ptr_tag, invalid_base_tag, int i) { return std::make_shared<TestFactory7>(i); },
            [](shared_ptr_tag, invalid_base_tag, int i) { return std::make_shared<TestFactory7>(i); })) // <-- invalid alias factory

        .def("get", &TestFactory7::get)
        .def("has_alias", &TestFactory7::has_alias)

        .def_static("get_cstats", &ConstructorStats::get<TestFactory7>, py::return_value_policy::reference)
        .def_static("get_alias_cstats", &ConstructorStats::get<PyTF7>, py::return_value_policy::reference)
        ;

    // test_placement_new_alternative
    // Class with a custom new operator but *without* a placement new operator (issue #948)
    class NoPlacementNew {
    public:
        NoPlacementNew(int i) : i(i) { }
        static void *operator new(std::size_t s) {
            auto *p = ::operator new(s);
            py::print("operator new called, returning", reinterpret_cast<uintptr_t>(p));
            return p;
        }
        static void operator delete(void *p) {
            py::print("operator delete called on", reinterpret_cast<uintptr_t>(p));
            ::operator delete(p);
        }
        int i;
    };
285
    // As of 2.2, `py::init<args>` no longer requires placement new
286
    py::class_<NoPlacementNew>(m, "NoPlacementNew")
287
        .def(py::init<int>())
288
289
290
291
292
293
294
295
        .def(py::init([]() { return new NoPlacementNew(100); }))
        .def_readwrite("i", &NoPlacementNew::i)
        ;


    // test_reallocations
    // Class that has verbose operator_new/operator_delete calls
    struct NoisyAlloc {
296
        NoisyAlloc(const NoisyAlloc &) = default;
297
298
299
300
301
302
303
304
305
306
307
308
309
        NoisyAlloc(int i) { py::print(py::str("NoisyAlloc(int {})").format(i)); }
        NoisyAlloc(double d) { py::print(py::str("NoisyAlloc(double {})").format(d)); }
        ~NoisyAlloc() { py::print("~NoisyAlloc()"); }

        static void *operator new(size_t s) { py::print("noisy new"); return ::operator new(s); }
        static void *operator new(size_t, void *p) { py::print("noisy placement new"); return p; }
        static void operator delete(void *p, size_t) { py::print("noisy delete"); ::operator delete(p); }
        static void operator delete(void *, void *) { py::print("noisy placement delete"); }
#if defined(_MSC_VER) && _MSC_VER < 1910
        // MSVC 2015 bug: the above "noisy delete" isn't invoked (fixed in MSVC 2017)
        static void operator delete(void *p) { py::print("noisy delete"); ::operator delete(p); }
#endif
    };
310
311
312


    py::class_<NoisyAlloc> pyNoisyAlloc(m, "NoisyAlloc");
313
314
315
        // Since these overloads have the same number of arguments, the dispatcher will try each of
        // them until the arguments convert.  Thus we can get a pre-allocation here when passing a
        // single non-integer:
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
    ignoreOldStyleInitWarnings([&pyNoisyAlloc]() {
        pyNoisyAlloc.def("__init__", [](NoisyAlloc *a, int i) { new (a) NoisyAlloc(i); }); // Regular constructor, runs first, requires preallocation
    });

    pyNoisyAlloc.def(py::init([](double d) { return new NoisyAlloc(d); }));

    // The two-argument version: first the factory pointer overload.
    pyNoisyAlloc.def(py::init([](int i, int) { return new NoisyAlloc(i); }));
    // Return-by-value:
    pyNoisyAlloc.def(py::init([](double d, int) { return NoisyAlloc(d); }));
    // Old-style placement new init; requires preallocation
    ignoreOldStyleInitWarnings([&pyNoisyAlloc]() {
        pyNoisyAlloc.def("__init__", [](NoisyAlloc &a, double d, double) { new (&a) NoisyAlloc(d); });
    });
    // Requires deallocation of previous overload preallocated value:
    pyNoisyAlloc.def(py::init([](int i, double) { return new NoisyAlloc(i); }));
    // Regular again: requires yet another preallocation
    ignoreOldStyleInitWarnings([&pyNoisyAlloc]() {
        pyNoisyAlloc.def("__init__", [](NoisyAlloc &a, int i, std::string) { new (&a) NoisyAlloc(i); });
    });
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354




    // static_assert testing (the following def's should all fail with appropriate compilation errors):
#if 0
    struct BadF1Base {};
    struct BadF1 : BadF1Base {};
    struct PyBadF1 : BadF1 {};
    py::class_<BadF1, PyBadF1, std::shared_ptr<BadF1>> bf1(m, "BadF1");
    // wrapped factory function must return a compatible pointer, holder, or value
    bf1.def(py::init([]() { return 3; }));
    // incompatible factory function pointer return type
    bf1.def(py::init([]() { static int three = 3; return &three; }));
    // incompatible factory function std::shared_ptr<T> return type: cannot convert shared_ptr<T> to holder
    // (non-polymorphic base)
    bf1.def(py::init([]() { return std::shared_ptr<BadF1Base>(new BadF1()); }));
#endif
}