pybind11/example/issues.cpp

178 lines
6.8 KiB
C++
Raw Normal View History

/*
example/issues.cpp -- collection of testcases for miscellaneous issues
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include "example.h"
Improve constructor/destructor tracking This commit rewrites the examples that look for constructor/destructor calls to do so via static variable tracking rather than output parsing. The added ConstructorStats class provides methods to keep track of constructors and destructors, number of default/copy/move constructors, and number of copy/move assignments. It also provides a mechanism for storing values (e.g. for value construction), and then allows all of this to be checked at the end of a test by getting the statistics for a C++ (or python mapping) class. By not relying on the precise pattern of constructions/destructions, but rather simply ensuring that every construction is matched with a destruction on the same object, we ensure that everything that gets created also gets destroyed as expected. This replaces all of the various "std::cout << whatever" code in constructors/destructors with `print_created(this)`/`print_destroyed(this)`/etc. functions which provide similar output, but now has a unified format across the different examples, including a new ### prefix that makes mixed example output and lifecycle events easier to distinguish. With this change, relaxed mode is no longer needed, which enables testing for proper destruction under MSVC, and under any other compiler that generates code calling extra constructors, or optimizes away any constructors. GCC/clang are used as the baseline for move constructors; the tests are adapted to allow more move constructors to be evoked (but other types are constructors much have matching counts). This commit also disables output buffering of tests, as the buffering sometimes results in C++ output ending up in the middle of python output (or vice versa), depending on the OS/python version.
2016-08-07 17:05:26 +00:00
#include "constructor-stats.h"
#include <pybind11/stl.h>
#include <pybind11/operators.h>
2016-04-30 21:02:39 +00:00
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>);
Improve constructor/destructor tracking This commit rewrites the examples that look for constructor/destructor calls to do so via static variable tracking rather than output parsing. The added ConstructorStats class provides methods to keep track of constructors and destructors, number of default/copy/move constructors, and number of copy/move assignments. It also provides a mechanism for storing values (e.g. for value construction), and then allows all of this to be checked at the end of a test by getting the statistics for a C++ (or python mapping) class. By not relying on the precise pattern of constructions/destructions, but rather simply ensuring that every construction is matched with a destruction on the same object, we ensure that everything that gets created also gets destroyed as expected. This replaces all of the various "std::cout << whatever" code in constructors/destructors with `print_created(this)`/`print_destroyed(this)`/etc. functions which provide similar output, but now has a unified format across the different examples, including a new ### prefix that makes mixed example output and lifecycle events easier to distinguish. With this change, relaxed mode is no longer needed, which enables testing for proper destruction under MSVC, and under any other compiler that generates code calling extra constructors, or optimizes away any constructors. GCC/clang are used as the baseline for move constructors; the tests are adapted to allow more move constructors to be evoked (but other types are constructors much have matching counts). This commit also disables output buffering of tests, as the buffering sometimes results in C++ output ending up in the middle of python output (or vice versa), depending on the OS/python version.
2016-08-07 17:05:26 +00:00
#define TRACKERS(CLASS) CLASS() { print_default_created(this); } ~CLASS() { print_destroyed(this); }
struct NestABase { int value = -2; TRACKERS(NestABase) };
struct NestA : NestABase { int value = 3; NestA& operator+=(int i) { value += i; return *this; } TRACKERS(NestA) };
struct NestB { NestA a; int value = 4; NestB& operator-=(int i) { value -= i; return *this; } TRACKERS(NestB) };
struct NestC { NestB b; int value = 5; NestC& operator*=(int i) { value *= i; return *this; } TRACKERS(NestC) };
void init_issues(py::module &m) {
py::module m2 = m.def_submodule("issues");
2016-05-01 08:39:45 +00:00
#if !defined(_MSC_VER)
// Visual Studio 2015 currently cannot compile this test
// (see the comment in type_caster_base::make_copy_constructor)
// #70 compilation issue if operator new is not public
class NonConstructible { private: void *operator new(size_t bytes) throw(); };
py::class_<NonConstructible>(m, "Foo");
m2.def("getstmt", []() -> NonConstructible * { return nullptr; },
2016-05-01 08:39:45 +00:00
py::return_value_policy::reference);
#endif
// #137: const char* isn't handled properly
m2.def("print_cchar", [](const char *string) { std::cout << string << std::endl; });
// #150: char bindings broken
m2.def("print_char", [](char c) { std::cout << c << std::endl; });
// #159: virtual function dispatch has problems with similar-named functions
2016-04-30 21:02:39 +00:00
struct Base { virtual void dispatch(void) const {
/* for some reason MSVC2015 can't compile this if the function is pure virtual */
}; };
2016-04-30 20:44:00 +00:00
struct DispatchIssue : Base {
virtual void dispatch(void) const {
PYBIND11_OVERLOAD_PURE(void, Base, dispatch, /* no arguments */);
}
};
py::class_<Base, std::unique_ptr<Base>, DispatchIssue>(m2, "DispatchIssue")
.def(py::init<>())
.def("dispatch", &Base::dispatch);
2016-04-30 20:44:00 +00:00
m2.def("dispatch_issue_go", [](const Base * b) { b->dispatch(); });
struct Placeholder { int i; Placeholder(int i) : i(i) { } };
py::class_<Placeholder>(m2, "Placeholder")
.def(py::init<int>())
.def("__repr__", [](const Placeholder &p) { return "Placeholder[" + std::to_string(p.i) + "]"; });
// #171: Can't return reference wrappers (or STL datastructures containing them)
m2.def("return_vec_of_reference_wrapper", [](std::reference_wrapper<Placeholder> p4){
Placeholder *p1 = new Placeholder{1};
Placeholder *p2 = new Placeholder{2};
Placeholder *p3 = new Placeholder{3};
std::vector<std::reference_wrapper<Placeholder>> v;
v.push_back(std::ref(*p1));
v.push_back(std::ref(*p2));
v.push_back(std::ref(*p3));
v.push_back(p4);
return v;
});
// #181: iterator passthrough did not compile
m2.def("iterator_passthrough", [](py::iterator s) -> py::iterator {
return py::make_iterator(std::begin(s), std::end(s));
});
2016-04-30 21:02:39 +00:00
// #187: issue involving std::shared_ptr<> return value policy & garbage collection
struct ElementBase { virtual void foo() { } /* Force creation of virtual table */ };
struct ElementA : ElementBase {
ElementA(int v) : v(v) { }
int value() { return v; }
int v;
};
struct ElementList {
void add(std::shared_ptr<ElementBase> e) { l.push_back(e); }
std::vector<std::shared_ptr<ElementBase>> l;
};
py::class_<ElementBase, std::shared_ptr<ElementBase>> (m2, "ElementBase");
py::class_<ElementA, std::shared_ptr<ElementA>>(m2, "ElementA", py::base<ElementBase>())
.def(py::init<int>())
.def("value", &ElementA::value);
py::class_<ElementList, std::shared_ptr<ElementList>>(m2, "ElementList")
.def(py::init<>())
.def("add", &ElementList::add)
.def("get", [](ElementList &el){
py::list list;
for (auto &e : el.l)
list.append(py::cast(e));
return list;
});
// (no id): should not be able to pass 'None' to a reference argument
m2.def("print_element", [](ElementA &el) { std::cout << el.value() << std::endl; });
// (no id): don't cast doubles to ints
m2.def("expect_float", [](float f) { return f; });
m2.def("expect_int", [](int i) { return i; });
// (no id): don't invoke Python dispatch code when instantiating C++
// classes that were not extended on the Python side
struct A {
virtual ~A() {}
virtual void f() { std::cout << "A.f()" << std::endl; }
};
struct PyA : A {
PyA() { std::cout << "PyA.PyA()" << std::endl; }
void f() override {
std::cout << "PyA.f()" << std::endl;
PYBIND11_OVERLOAD(void, A, f);
}
};
auto call_f = [](A *a) { a->f(); };
2016-07-01 12:54:24 +00:00
pybind11::class_<A, std::unique_ptr<A>, PyA>(m2, "A")
.def(py::init<>())
.def("f", &A::f);
2016-07-01 12:54:24 +00:00
m2.def("call_f", call_f);
try {
py::class_<Placeholder>(m2, "Placeholder");
throw std::logic_error("Expected an exception!");
} catch (std::runtime_error &) {
/* All good */
}
// Issue #283: __str__ called on uninitialized instance when constructor arguments invalid
class StrIssue {
public:
StrIssue(int i) : val{i} {}
StrIssue() : StrIssue(-1) {}
int value() const { return val; }
private:
int val;
};
py::class_<StrIssue> si(m2, "StrIssue");
si .def(py::init<int>())
.def(py::init<>())
.def("__str__", [](const StrIssue &si) {
std::cout << "StrIssue.__str__ called" << std::endl;
return "StrIssue[" + std::to_string(si.value()) + "]";
})
;
// Issue #328: first member in a class can't be used in operators
py::class_<NestABase>(m2, "NestABase").def(py::init<>()).def_readwrite("value", &NestABase::value);
py::class_<NestA>(m2, "NestA").def(py::init<>()).def(py::self += int())
.def("as_base", [](NestA &a) -> NestABase& { return (NestABase&) a; }, py::return_value_policy::reference_internal);
py::class_<NestB>(m2, "NestB").def(py::init<>()).def(py::self -= int()).def_readwrite("a", &NestB::a);
py::class_<NestC>(m2, "NestC").def(py::init<>()).def(py::self *= int()).def_readwrite("b", &NestC::b);
m2.def("print_NestA", [](const NestA &a) { std::cout << a.value << std::endl; });
m2.def("print_NestB", [](const NestB &b) { std::cout << b.value << std::endl; });
m2.def("print_NestC", [](const NestC &c) { std::cout << c.value << std::endl; });
}