Commit f4f2afb6 authored by Wenzel Jakob's avatar Wenzel Jakob Committed by GitHub
Browse files

Merge pull request #324 from jagerman/example-constructor-tracking

Improve constructor/destructor tracking
parents 85557b1d 3f589379
......@@ -60,9 +60,6 @@ foreach(CompilerFlag ${CompilerFlags})
endforeach()
set(RUN_TEST ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/run_test.py)
if(MSVC OR CMAKE_CXX_COMPILER_ID MATCHES "Intel")
set(RUN_TEST ${RUN_TEST} --relaxed)
endif()
foreach(VALUE ${PYBIND11_EXAMPLES})
string(REGEX REPLACE "^(.+).cpp$" "\\1" EXAMPLE_NAME "${VALUE}")
......
#pragma once
/*
example/constructor-stats.h -- framework for printing and tracking object
instance lifetimes in example/test code.
Copyright (c) 2016 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.
This header provides a few useful tools for writing examples or tests that want to check and/or
display object instance lifetimes. It requires that you include this header and add the following
function calls to constructors:
class MyClass {
MyClass() { ...; print_default_created(this); }
~MyClass() { ...; print_destroyed(this); }
MyClass(const MyClass &c) { ...; print_copy_created(this); }
MyClass(MyClass &&c) { ...; print_move_created(this); }
MyClass(int a, int b) { ...; print_created(this, a, b); }
MyClass &operator=(const MyClass &c) { ...; print_copy_assigned(this); }
MyClass &operator=(MyClass &&c) { ...; print_move_assigned(this); }
...
}
You can find various examples of these in several of the existing example .cpp files. (Of course
you don't need to add any of the above constructors/operators that you don't actually have, except
for the destructor).
Each of these will print an appropriate message such as:
### MyClass @ 0x2801910 created via default constructor
### MyClass @ 0x27fa780 created 100 200
### MyClass @ 0x2801910 destroyed
### MyClass @ 0x27fa780 destroyed
You can also include extra arguments (such as the 100, 200 in the output above, coming from the
value constructor) for all of the above methods which will be included in the output.
For testing, each of these also keeps track the created instances and allows you to check how many
of the various constructors have been invoked from the Python side via code such as:
from example import ConstructorStats
cstats = ConstructorStats.get(MyClass)
print(cstats.alive())
print(cstats.default_constructions)
Note that `.alive()` should usually be the first thing you call as it invokes Python's garbage
collector to actually destroy objects that aren't yet referenced.
For everything except copy and move constructors and destructors, any extra values given to the
print_...() function is stored in a class-specific values list which you can retrieve and inspect
from the ConstructorStats instance `.values()` method.
In some cases, when you need to track instances of a C++ class not registered with pybind11, you
need to add a function returning the ConstructorStats for the C++ class; this can be done with:
m.def("get_special_cstats", &ConstructorStats::get<SpecialClass>, py::return_value_policy::reference_internal)
Finally, you can suppress the output messages, but keep the constructor tracking (for
inspection/testing in python) by using the functions with `print_` replaced with `track_` (e.g.
`track_copy_created(this)`).
*/
#include "example.h"
#include <unordered_map>
#include <list>
#include <typeindex>
#include <sstream>
class ConstructorStats {
protected:
std::unordered_map<void*, int> _instances; // Need a map rather than set because members can shared address with parents
std::list<std::string> _values; // Used to track values (e.g. of value constructors)
public:
int default_constructions = 0;
int copy_constructions = 0;
int move_constructions = 0;
int copy_assignments = 0;
int move_assignments = 0;
void copy_created(void *inst) {
created(inst);
copy_constructions++;
}
void move_created(void *inst) {
created(inst);
move_constructions++;
}
void default_created(void *inst) {
created(inst);
default_constructions++;
}
void created(void *inst) {
++_instances[inst];
};
void destroyed(void *inst) {
if (--_instances[inst] < 0)
throw std::runtime_error("cstats.destroyed() called with unknown instance; potential double-destruction or a missing cstats.created()");
}
int alive() {
// Force garbage collection to ensure any pending destructors are invoked:
py::module::import("gc").attr("collect").operator py::object()();
int total = 0;
for (const auto &p : _instances) if (p.second > 0) total += p.second;
return total;
}
void value() {} // Recursion terminator
// Takes one or more values, converts them to strings, then stores them.
template <typename T, typename... Tmore> void value(const T &v, Tmore &&...args) {
std::ostringstream oss;
oss << v;
_values.push_back(oss.str());
value(std::forward<Tmore>(args)...);
}
py::list values() {
py::list l;
for (const auto &v : _values) l.append(py::cast(v));
return l;
}
// Gets constructor stats from a C++ type index
static ConstructorStats& get(std::type_index type) {
static std::unordered_map<std::type_index, ConstructorStats> all_cstats;
return all_cstats[type];
}
// Gets constructor stats from a C++ type
template <typename T> static ConstructorStats& get() {
return get(typeid(T));
}
// Gets constructor stats from a Python class
static ConstructorStats& get(py::object class_) {
auto &internals = py::detail::get_internals();
const std::type_index *t1 = nullptr, *t2 = nullptr;
try {
auto *type_info = internals.registered_types_py.at(class_.ptr());
for (auto &p : internals.registered_types_cpp) {
if (p.second == type_info) {
if (t1) {
t2 = &p.first;
break;
}
t1 = &p.first;
}
}
}
catch (std::out_of_range) {}
if (!t1) throw std::runtime_error("Unknown class passed to ConstructorStats::get()");
auto &cs1 = get(*t1);
// If we have both a t1 and t2 match, one is probably the trampoline class; return whichever
// has more constructions (typically one or the other will be 0)
if (t2) {
auto &cs2 = get(*t2);
int cs1_total = cs1.default_constructions + cs1.copy_constructions + cs1.move_constructions + (int) cs1._values.size();
int cs2_total = cs2.default_constructions + cs2.copy_constructions + cs2.move_constructions + (int) cs2._values.size();
if (cs2_total > cs1_total) return cs2;
}
return cs1;
}
};
// To track construction/destruction, you need to call these methods from the various
// constructors/operators. The ones that take extra values record the given values in the
// constructor stats values for later inspection.
template <class T> void track_copy_created(T *inst) { ConstructorStats::get<T>().copy_created(inst); }
template <class T> void track_move_created(T *inst) { ConstructorStats::get<T>().move_created(inst); }
template <class T, typename... Values> void track_copy_assigned(T *, Values &&...values) {
auto &cst = ConstructorStats::get<T>();
cst.copy_assignments++;
cst.value(std::forward<Values>(values)...);
}
template <class T, typename... Values> void track_move_assigned(T *, Values &&...values) {
auto &cst = ConstructorStats::get<T>();
cst.move_assignments++;
cst.value(std::forward<Values>(values)...);
}
template <class T, typename... Values> void track_default_created(T *inst, Values &&...values) {
auto &cst = ConstructorStats::get<T>();
cst.default_created(inst);
cst.value(std::forward<Values>(values)...);
}
template <class T, typename... Values> void track_created(T *inst, Values &&...values) {
auto &cst = ConstructorStats::get<T>();
cst.created(inst);
cst.value(std::forward<Values>(values)...);
}
template <class T, typename... Values> void track_destroyed(T *inst) {
ConstructorStats::get<T>().destroyed(inst);
}
template <class T, typename... Values> void track_values(T *, Values &&...values) {
ConstructorStats::get<T>().value(std::forward<Values>(values)...);
}
inline void print_constr_details_more() { std::cout << std::endl; }
template <typename Head, typename... Tail> void print_constr_details_more(const Head &head, Tail &&...tail) {
std::cout << " " << head;
print_constr_details_more(std::forward<Tail>(tail)...);
}
template <class T, typename... Output> void print_constr_details(T *inst, const std::string &action, Output &&...output) {
std::cout << "### " << py::type_id<T>() << " @ " << inst << " " << action;
print_constr_details_more(std::forward<Output>(output)...);
}
// Verbose versions of the above:
template <class T, typename... Values> void print_copy_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values
print_constr_details(inst, "created via copy constructor", values...);
track_copy_created(inst);
}
template <class T, typename... Values> void print_move_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values
print_constr_details(inst, "created via move constructor", values...);
track_move_created(inst);
}
template <class T, typename... Values> void print_copy_assigned(T *inst, Values &&...values) {
print_constr_details(inst, "assigned via copy assignment", values...);
track_copy_assigned(inst, values...);
}
template <class T, typename... Values> void print_move_assigned(T *inst, Values &&...values) {
print_constr_details(inst, "assigned via move assignment", values...);
track_move_assigned(inst, values...);
}
template <class T, typename... Values> void print_default_created(T *inst, Values &&...values) {
print_constr_details(inst, "created via default constructor", values...);
track_default_created(inst, values...);
}
template <class T, typename... Values> void print_created(T *inst, Values &&...values) {
print_constr_details(inst, "created", values...);
track_created(inst, values...);
}
template <class T, typename... Values> void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values
print_constr_details(inst, "destroyed", values...);
track_destroyed(inst);
}
template <class T, typename... Values> void print_values(T *inst, Values &&...values) {
print_constr_details(inst, ":", values...);
track_values(inst, values...);
}
......@@ -8,35 +8,36 @@
*/
#include "example.h"
#include "constructor-stats.h"
class Matrix {
public:
Matrix(size_t rows, size_t cols) : m_rows(rows), m_cols(cols) {
std::cout << "Value constructor: Creating a " << rows << "x" << cols << " matrix " << std::endl;
print_created(this, std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix");
m_data = new float[rows*cols];
memset(m_data, 0, sizeof(float) * rows * cols);
}
Matrix(const Matrix &s) : m_rows(s.m_rows), m_cols(s.m_cols) {
std::cout << "Copy constructor: Creating a " << m_rows << "x" << m_cols << " matrix " << std::endl;
print_copy_created(this, std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix");
m_data = new float[m_rows * m_cols];
memcpy(m_data, s.m_data, sizeof(float) * m_rows * m_cols);
}
Matrix(Matrix &&s) : m_rows(s.m_rows), m_cols(s.m_cols), m_data(s.m_data) {
std::cout << "Move constructor: Creating a " << m_rows << "x" << m_cols << " matrix " << std::endl;
print_move_created(this);
s.m_rows = 0;
s.m_cols = 0;
s.m_data = nullptr;
}
~Matrix() {
std::cout << "Freeing a " << m_rows << "x" << m_cols << " matrix " << std::endl;
print_destroyed(this, std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix");
delete[] m_data;
}
Matrix &operator=(const Matrix &s) {
std::cout << "Assignment operator : Creating a " << s.m_rows << "x" << s.m_cols << " matrix " << std::endl;
print_copy_assigned(this, std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix");
delete[] m_data;
m_rows = s.m_rows;
m_cols = s.m_cols;
......@@ -46,7 +47,7 @@ public:
}
Matrix &operator=(Matrix &&s) {
std::cout << "Move assignment operator : Creating a " << s.m_rows << "x" << s.m_cols << " matrix " << std::endl;
print_move_assigned(this, std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix");
if (&s != this) {
delete[] m_data;
m_rows = s.m_rows; m_cols = s.m_cols; m_data = s.m_data;
......@@ -111,5 +112,6 @@ void init_ex_buffers(py::module &m) {
{ sizeof(float) * m.rows(), /* Strides (in bytes) for each index */
sizeof(float) }
);
});
})
;
}
......@@ -30,3 +30,16 @@ for i in range(m4.rows()):
for j in range(m4.cols()):
print(m4[i, j], end = ' ')
print()
from example import ConstructorStats
cstats = ConstructorStats.get(Matrix)
print("Instances not destroyed:", cstats.alive())
m = m4 = None
print("Instances not destroyed:", cstats.alive())
m2 = None # m2 holds an m reference
print("Instances not destroyed:", cstats.alive())
print("Constructor values:", cstats.values())
print("Copy constructions:", cstats.copy_constructions)
#print("Move constructions:", cstats.move_constructions >= 0) # Don't invoke any
print("Copy assignments:", cstats.copy_assignments)
print("Move assignments:", cstats.move_assignments)
Value constructor: Creating a 5x5 matrix
### Matrix @ 0x1df1920 created 5x5 matrix
0.0
4.0
[[ 0. 0. 0. 0. 0.]
......@@ -10,8 +10,15 @@ Value constructor: Creating a 5x5 matrix
5.0
[[ 1. 2. 3.]
[ 4. 5. 6.]]
Value constructor: Creating a 2x3 matrix
### Matrix @ 0x1fa8cf0 created 2x3 matrix
1.0 2.0 3.0
4.0 5.0 6.0
Freeing a 2x3 matrix
Freeing a 5x5 matrix
Instances not destroyed: 2
### Matrix @ 0x1fa8cf0 destroyed 2x3 matrix
Instances not destroyed: 1
### Matrix @ 0x1df1920 destroyed 5x5 matrix
Instances not destroyed: 0
Constructor values: ['5x5 matrix', '2x3 matrix']
Copy constructions: 0
Copy assignments: 0
Move assignments: 0
......@@ -8,6 +8,7 @@
*/
#include "example.h"
#include "constructor-stats.h"
#include <pybind11/functional.h>
......@@ -57,6 +58,21 @@ void test_dummy_function(const std::function<int(int)> &f) {
}
}
struct Payload {
Payload() {
print_default_created(this);
}
~Payload() {
print_destroyed(this);
}
Payload(const Payload &) {
print_copy_created(this);
}
Payload(Payload &&) {
print_move_created(this);
}
};
void init_ex_callbacks(py::module &m) {
m.def("test_callback1", &test_callback1);
m.def("test_callback2", &test_callback2);
......@@ -66,21 +82,6 @@ void init_ex_callbacks(py::module &m) {
/* Test cleanup of lambda closure */
struct Payload {
Payload() {
std::cout << "Payload constructor" << std::endl;
}
~Payload() {
std::cout << "Payload destructor" << std::endl;
}
Payload(const Payload &) {
std::cout << "Payload copy constructor" << std::endl;
}
Payload(Payload &&) {
std::cout << "Payload move constructor" << std::endl;
}
};
m.def("test_cleanup", []() -> std::function<void(void)> {
Payload p;
......@@ -94,4 +95,6 @@ void init_ex_callbacks(py::module &m) {
m.def("dummy_function2", &dummy_function2);
m.def("roundtrip", &roundtrip);
m.def("test_dummy_function", &test_dummy_function);
// Export the payload constructor statistics for testing purposes:
m.def("payload_cstats", &ConstructorStats::get<Payload>);
}
......@@ -55,6 +55,12 @@ print("func(number=43) = %i" % f(number=43))
test_cleanup()
from example import payload_cstats
cstats = payload_cstats()
print("Payload instances not destroyed:", cstats.alive())
print("Copy constructions:", cstats.copy_constructions)
print("Move constructions:", cstats.move_constructions >= 1)
from example import dummy_function
from example import dummy_function2
from example import test_dummy_function
......
......@@ -7,7 +7,7 @@ Molly is a dog
Woof!
The following error is expected: Incompatible function arguments. The following argument types are supported:
1. (arg0: example.Dog) -> None
Invoked with: <example.Pet object at 0>
Invoked with: <example.Pet object at 0x7ffaf4b00db0>
Callback function 1 called!
False
Callback function 2 called : Hello, x, True, 5
......@@ -19,12 +19,15 @@ False
func(43) = 44
func(43) = 44
func(number=43) = 44
Payload constructor
Payload copy constructor
Payload move constructor
Payload destructor
Payload destructor
Payload destructor
### Payload @ 0x7ffdcee09c80 created via default constructor
### Payload @ 0x7ffdcee09c88 created via copy constructor
### Payload @ 0xb54500 created via move constructor
### Payload @ 0x7ffdcee09c88 destroyed
### Payload @ 0x7ffdcee09c80 destroyed
### Payload @ 0xb54500 destroyed
Payload instances not destroyed: 0
Copy constructions: 1
Move constructions: True
argument matches dummy_function
eval(1) = 2
roundtrip..
......@@ -36,6 +39,7 @@ could not convert to a function pointer.
All OK!
could not convert to a function pointer.
All OK!
test_callback3(arg0: Callable[[int], int]) -> None
test_callback4() -> Callable[[int], int]
......@@ -8,32 +8,25 @@
BSD-style license that can be found in the LICENSE file.
*/
#include <unordered_map>
#include <list>
#include "example.h"
#include "constructor-stats.h"
class ExampleMandA {
public:
ExampleMandA() {
cout << "Called ExampleMandA default constructor.." << endl;
}
ExampleMandA(int value) : value(value) {
cout << "Called ExampleMandA constructor with value " << value << ".." << endl;
}
ExampleMandA(const ExampleMandA &e) : value(e.value) {
cout << "Called ExampleMandA copy constructor with value " << value << ".." << endl;
}
ExampleMandA(ExampleMandA &&e) : value(e.value) {
cout << "Called ExampleMandA move constructor with value " << value << ".." << endl;
e.value = 0;
}
~ExampleMandA() {
cout << "Called ExampleMandA destructor (" << value << ")" << endl;
}
ExampleMandA() { print_default_created(this); }
ExampleMandA(int value) : value(value) { print_created(this, value); }
ExampleMandA(const ExampleMandA &e) : value(e.value) { print_copy_created(this); }
ExampleMandA(ExampleMandA &&e) : value(e.value) { print_move_created(this); }
~ExampleMandA() { print_destroyed(this); }
std::string toString() {
return "ExampleMandA[value=" + std::to_string(value) + "]";
}
void operator=(const ExampleMandA &e) { cout << "Assignment operator" << endl; value = e.value; }
void operator=(ExampleMandA &&e) { cout << "Move assignment operator" << endl; value = e.value; e.value = 0;}
void operator=(const ExampleMandA &e) { print_copy_assigned(this); value = e.value; }
void operator=(ExampleMandA &&e) { print_move_assigned(this); value = e.value; }
void add1(ExampleMandA other) { value += other.value; } // passing by value
void add2(ExampleMandA &other) { value += other.value; } // passing by reference
......@@ -88,5 +81,6 @@ void init_ex_methods_and_attributes(py::module &m) {
.def("internal4", &ExampleMandA::internal4)
.def("internal5", &ExampleMandA::internal5)
.def("__str__", &ExampleMandA::toString)
.def_readwrite("value", &ExampleMandA::value);
.def_readwrite("value", &ExampleMandA::value)
;
}
......@@ -35,3 +35,16 @@ print(instance1.internal5())
print("Instance 1, direct access = %i" % instance1.value)
instance1.value = 100
print("Instance 1: " + str(instance1))
from example import ConstructorStats
cstats = ConstructorStats.get(ExampleMandA)
print("Instances not destroyed:", cstats.alive())
instance1 = instance2 = None
print("Instances not destroyed:", cstats.alive())
print("Constructor values:", cstats.values())
print("Default constructions:", cstats.default_constructions)
print("Copy constructions:", cstats.copy_constructions)
print("Move constructions:", cstats.move_constructions >= 1)
print("Copy assignments:", cstats.copy_assignments)
print("Move assignments:", cstats.move_assignments)
Called ExampleMandA default constructor..
Called ExampleMandA constructor with value 32..
Called ExampleMandA copy constructor with value 32..
Called ExampleMandA copy constructor with value 32..
Called ExampleMandA destructor (32)
Called ExampleMandA destructor (32)
### ExampleMandA @ 0x2801910 created via default constructor
### ExampleMandA @ 0x27fa780 created 32
### ExampleMandA @ 0x7fff80a98a74 created via copy constructor
### ExampleMandA @ 0x7fff80a98a78 created via copy constructor
### ExampleMandA @ 0x7fff80a98a78 destroyed
### ExampleMandA @ 0x7fff80a98a74 destroyed
Instance 1: ExampleMandA[value=320]
Instance 2: ExampleMandA[value=32]
Called ExampleMandA copy constructor with value 320..
Called ExampleMandA move constructor with value 320..
Called ExampleMandA destructor (0)
### ExampleMandA @ 0x7fff80a98a84 created via copy constructor
### ExampleMandA @ 0x2801fd0 created via move constructor
### ExampleMandA @ 0x7fff80a98a84 destroyed
ExampleMandA[value=320]
Called ExampleMandA destructor (320)
### ExampleMandA @ 0x2801fd0 destroyed
ExampleMandA[value=320]
ExampleMandA[value=320]
ExampleMandA[value=320]
......@@ -22,5 +22,13 @@ ExampleMandA[value=320]
320
Instance 1, direct access = 320
Instance 1: ExampleMandA[value=100]
Called ExampleMandA destructor (32)
Called ExampleMandA destructor (100)
Instances not destroyed: 2
### ExampleMandA @ 0x2801910 destroyed
### ExampleMandA @ 0x27fa780 destroyed
Instances not destroyed: 0
Constructor values: ['32']
Default constructions: 1
Copy constructions: 3
Move constructions: True
Copy assignments: 0
Move assignments: 0
......@@ -9,6 +9,7 @@
*/
#include "example.h"
#include "constructor-stats.h"
void submodule_func() {
std::cout << "submodule_func()" << std::endl;
......@@ -16,9 +17,10 @@ void submodule_func() {
class A {
public:
A(int v) : v(v) { std::cout << "A constructor" << std::endl; }
~A() { std::cout << "A destructor" << std::endl; }
A(const A&) { std::cout << "A copy constructor" << std::endl; }
A(int v) : v(v) { print_created(this, v); }
~A() { print_destroyed(this); }
A(const A&) { print_copy_created(this); }
A& operator=(const A &copy) { print_copy_assigned(this); v = copy.v; return *this; }
std::string toString() { return "A[" + std::to_string(v) + "]"; }
private:
int v;
......@@ -26,9 +28,10 @@ private:
class B {
public:
B() { std::cout << "B constructor" << std::endl; }
~B() { std::cout << "B destructor" << std::endl; }
B(const B&) { std::cout << "B copy constructor" << std::endl; }
B() { print_default_created(this); }
~B() { print_destroyed(this); }
B(const B&) { print_copy_created(this); }
B& operator=(const B &copy) { print_copy_assigned(this); a1 = copy.a1; a2 = copy.a2; return *this; }
A &get_a1() { return a1; }
A &get_a2() { return a2; }
......
......@@ -28,3 +28,16 @@ print(b.get_a2())
print(b.a2)
print(OD([(1, 'a'), (2, 'b')]))
from example import ConstructorStats
cstats = [ConstructorStats.get(A), ConstructorStats.get(B)]
print("Instances not destroyed:", [x.alive() for x in cstats])
b = None
print("Instances not destroyed:", [x.alive() for x in cstats])
print("Constructor values:", [x.values() for x in cstats])
print("Default constructions:", [x.default_constructions for x in cstats])
print("Copy constructions:", [x.copy_constructions for x in cstats])
#print("Move constructions:", [x.move_constructions >= 0 for x in cstats]) # Don't invoke any
print("Copy assignments:", [x.copy_assignments for x in cstats])
print("Move assignments:", [x.move_assignments for x in cstats])
example
example.submodule
submodule_func()
A constructor
A constructor
B constructor
### A @ 0x21a5bc0 created 1
### A @ 0x21a5bc4 created 2
### B @ 0x21a5bc0 created via default constructor
A[1]
A[1]
A[2]
A[2]
A constructor
A destructor
A constructor
A destructor
### A @ 0x20f93b0 created 42
### A @ 0x21a5bc0 assigned via copy assignment
### A @ 0x20f93b0 destroyed
### A @ 0x20f93d0 created 43
### A @ 0x21a5bc4 assigned via copy assignment
### A @ 0x20f93d0 destroyed
A[42]
A[42]
A[43]
A[43]
OrderedDict([(1, 'a'), (2, 'b')])
B destructor
A destructor
A destructor
Instances not destroyed: [2, 1]
### B @ 0x21a5bc0 destroyed
### A @ 0x21a5bc4 destroyed
### A @ 0x21a5bc0 destroyed
Instances not destroyed: [0, 0]
Constructor values: [['1', '2', '42', '43'], []]
Default constructions: [0, 1]
Copy constructions: [0, 0]
Copy assignments: [2, 0]
Move assignments: [0, 0]
......@@ -34,6 +34,8 @@ print_opaque_list(cvp.stringList)
print_void_ptr(return_void_ptr())
print_void_ptr(ExampleMandA()) # Should also work for other C++ types
from example import ConstructorStats
print("ExampleMandA still alive:", ConstructorStats.get(ExampleMandA).alive())
try:
print_void_ptr([1, 2, 3]) # This should not work
......
......@@ -6,13 +6,14 @@ Opaque list: [Element 1]
Opaque list: []
Opaque list: [Element 1, Element 3]
Got void ptr : 0x1234
Called ExampleMandA default constructor..
Got void ptr : 0x7f9ba0f3c430
Called ExampleMandA destructor (0)
### ExampleMandA @ 0x2ac5370 created via default constructor
Got void ptr : 0x2ac5370
### ExampleMandA @ 0x2ac5370 destroyed
ExampleMandA still alive: 0
Caught expected exception: Incompatible function arguments. The following argument types are supported:
1. (arg0: capsule) -> None
Invoked with: [1, 2, 3]
None
Got null str : 0x0
<example.StringList object at 0x10d3277a0>
<example.StringList object at 0x7f7ecde6fc00>
Opaque list: [some value]
......@@ -8,27 +8,28 @@
*/
#include "example.h"
#include "constructor-stats.h"
#include <pybind11/operators.h>
class Vector2 {
public:
Vector2(float x, float y) : x(x), y(y) { std::cout << "Value constructor" << std::endl; }
Vector2(const Vector2 &v) : x(v.x), y(v.y) { std::cout << "Copy constructor" << std::endl; }
Vector2(Vector2 &&v) : x(v.x), y(v.y) { std::cout << "Move constructor" << std::endl; v.x = v.y = 0; }
~Vector2() { std::cout << "Destructor." << std::endl; }
Vector2(float x, float y) : x(x), y(y) { print_created(this, toString()); }
Vector2(const Vector2 &v) : x(v.x), y(v.y) { print_copy_created(this); }
Vector2(Vector2 &&v) : x(v.x), y(v.y) { print_move_created(this); v.x = v.y = 0; }
~Vector2() { print_destroyed(this); }
std::string toString() const {
return "[" + std::to_string(x) + ", " + std::to_string(y) + "]";
}
void operator=(const Vector2 &v) {
cout << "Assignment operator" << endl;
print_copy_assigned(this);
x = v.x;
y = v.y;
}
void operator=(Vector2 &&v) {
cout << "Move assignment operator" << endl;
print_move_assigned(this);
x = v.x; y = v.y; v.x = v.y = 0;
}
......@@ -51,7 +52,6 @@ private:
float x, y;
};
void init_ex_operator_overloading(py::module &m) {
py::class_<Vector2>(m, "Vector2")
.def(py::init<float, float>())
......@@ -69,7 +69,8 @@ void init_ex_operator_overloading(py::module &m) {
.def(float() - py::self)
.def(float() * py::self)
.def(float() / py::self)
.def("__str__", &Vector2::toString);
.def("__str__", &Vector2::toString)
;
m.attr("Vector") = m.attr("Vector2");
}
......@@ -25,3 +25,17 @@ v1 += v2
v1 *= 2
print("(v1+v2)*2 = " + str(v1))
from example import ConstructorStats
cstats = ConstructorStats.get(Vector2)
print("Instances not destroyed:", cstats.alive())
v1 = None
print("Instances not destroyed:", cstats.alive())
v2 = None
print("Instances not destroyed:", cstats.alive())
print("Constructor values:", cstats.values())
print("Default constructions:", cstats.default_constructions)
print("Copy constructions:", cstats.copy_constructions)
print("Move constructions:", cstats.move_constructions >= 10)
print("Copy assignments:", cstats.copy_assignments)
print("Move assignments:", cstats.move_assignments)
Value constructor
Value constructor
### Vector2 @ 0x11f7830 created [1.000000, 2.000000]
### Vector2 @ 0x11427c0 created [3.000000, -1.000000]
v1 = [1.000000, 2.000000]
v2 = [3.000000, -1.000000]
Value constructor
Move constructor
Destructor.
Destructor.
### Vector2 @ 0x7ffef6b144b8 created [4.000000, 1.000000]
### Vector2 @ 0x11f7e90 created via move constructor
### Vector2 @ 0x7ffef6b144b8 destroyed
### Vector2 @ 0x11f7e90 destroyed
v1+v2 = [4.000000, 1.000000]
Value constructor
Move constructor
Destructor.
Destructor.
### Vector2 @ 0x7ffef6b144b8 created [-2.000000, 3.000000]
### Vector2 @ 0x11f7e90 created via move constructor
### Vector2 @ 0x7ffef6b144b8 destroyed
### Vector2 @ 0x11f7e90 destroyed
v1-v2 = [-2.000000, 3.000000]
Value constructor
Move constructor
Destructor.
Destructor.
### Vector2 @ 0x7ffef6b144c8 created [-7.000000, -6.000000]
### Vector2 @ 0x1115760 created via move constructor
### Vector2 @ 0x7ffef6b144c8 destroyed
### Vector2 @ 0x1115760 destroyed
v1-8 = [-7.000000, -6.000000]
Value constructor
Move constructor
Destructor.
Destructor.
### Vector2 @ 0x7ffef6b144c8 created [9.000000, 10.000000]
### Vector2 @ 0x1115760 created via move constructor
### Vector2 @ 0x7ffef6b144c8 destroyed
### Vector2 @ 0x1115760 destroyed
v1+8 = [9.000000, 10.000000]
Value constructor
Move constructor
Destructor.
Destructor.
### Vector2 @ 0x7ffef6b144b8 created [8.000000, 16.000000]
### Vector2 @ 0x1115760 created via move constructor
### Vector2 @ 0x7ffef6b144b8 destroyed
### Vector2 @ 0x1115760 destroyed
v1*8 = [8.000000, 16.000000]
Value constructor
Move constructor
Destructor.
Destructor.
### Vector2 @ 0x7ffef6b144a8 created [0.125000, 0.250000]
### Vector2 @ 0x112f150 created via move constructor
### Vector2 @ 0x7ffef6b144a8 destroyed
### Vector2 @ 0x112f150 destroyed
v1/8 = [0.125000, 0.250000]
Value constructor
Move constructor
Destructor.
Destructor.
### Vector2 @ 0x7ffef6b144f8 created [7.000000, 6.000000]
### Vector2 @ 0x112f1b0 created via move constructor
### Vector2 @ 0x7ffef6b144f8 destroyed
### Vector2 @ 0x112f1b0 destroyed
8-v1 = [7.000000, 6.000000]
Value constructor
Move constructor
Destructor.
Destructor.
### Vector2 @ 0x7ffef6b144f8 created [9.000000, 10.000000]
### Vector2 @ 0x112f1b0 created via move constructor
### Vector2 @ 0x7ffef6b144f8 destroyed
### Vector2 @ 0x112f1b0 destroyed
8+v1 = [9.000000, 10.000000]
Value constructor
Move constructor
Destructor.
Destructor.
### Vector2 @ 0x7ffef6b144e8 created [8.000000, 16.000000]
### Vector2 @ 0x112f230 created via move constructor
### Vector2 @ 0x7ffef6b144e8 destroyed
### Vector2 @ 0x112f230 destroyed
8*v1 = [8.000000, 16.000000]
Value constructor
Move constructor
Destructor.
Destructor.
### Vector2 @ 0x7ffef6b144d8 created [8.000000, 4.000000]
### Vector2 @ 0x11fb360 created via move constructor
### Vector2 @ 0x7ffef6b144d8 destroyed
### Vector2 @ 0x11fb360 destroyed
8/v1 = [8.000000, 4.000000]
(v1+v2)*2 = [8.000000, 2.000000]
Destructor.
Destructor.
Instances not destroyed: 2
### Vector2 @ 0x11f7830 destroyed
Instances not destroyed: 1
### Vector2 @ 0x11427c0 destroyed
Instances not destroyed: 0
Constructor values: ['[1.000000, 2.000000]', '[3.000000, -1.000000]', '[4.000000, 1.000000]', '[-2.000000, 3.000000]', '[-7.000000, -6.000000]', '[9.000000, 10.000000]', '[8.000000, 16.000000]', '[0.125000, 0.250000]', '[7.000000, 6.000000]', '[9.000000, 10.000000]', '[8.000000, 16.000000]', '[8.000000, 4.000000]']
Default constructions: 0
Copy constructions: 0
Move constructions: True
Copy assignments: 0
Move assignments: 0
......@@ -9,6 +9,7 @@
*/
#include "example.h"
#include "constructor-stats.h"
#include <pybind11/stl.h>
#ifdef _WIN32
......@@ -19,11 +20,11 @@
class ExamplePythonTypes {
public:
static ExamplePythonTypes *new_instance() {
return new ExamplePythonTypes();
}
~ExamplePythonTypes() {
std::cout << "Destructing ExamplePythonTypes" << std::endl;
auto *ptr = new ExamplePythonTypes();
print_created(ptr, "via new_instance");
return ptr;
}
~ExamplePythonTypes() { print_destroyed(this); }
/* Create and return a Python dictionary */
py::dict get_dict() {
......@@ -168,5 +169,6 @@ void init_ex_python_types(py::module &m) {
.def("throw_exception", &ExamplePythonTypes::throw_exception, "Throw an exception")
.def_static("new_instance", &ExamplePythonTypes::new_instance, "Return an instance")
.def_readwrite_static("value", &ExamplePythonTypes::value, "Static value member")
.def_readonly_static("value2", &ExamplePythonTypes::value2, "Static value member (readonly)");
.def_readonly_static("value2", &ExamplePythonTypes::value2, "Static value member (readonly)")
;
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment