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.
This commit is contained in:
Jason Rhinelander 2016-08-07 13:05:26 -04:00
parent 85557b1dec
commit 3f589379ec
36 changed files with 969 additions and 495 deletions

View File

@ -60,9 +60,6 @@ foreach(CompilerFlag ${CompilerFlags})
endforeach() endforeach()
set(RUN_TEST ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/run_test.py) 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}) foreach(VALUE ${PYBIND11_EXAMPLES})
string(REGEX REPLACE "^(.+).cpp$" "\\1" EXAMPLE_NAME "${VALUE}") string(REGEX REPLACE "^(.+).cpp$" "\\1" EXAMPLE_NAME "${VALUE}")

243
example/constructor-stats.h Normal file
View File

@ -0,0 +1,243 @@
#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...);
}

View File

@ -8,35 +8,36 @@
*/ */
#include "example.h" #include "example.h"
#include "constructor-stats.h"
class Matrix { class Matrix {
public: public:
Matrix(size_t rows, size_t cols) : m_rows(rows), m_cols(cols) { 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]; m_data = new float[rows*cols];
memset(m_data, 0, sizeof(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) { 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]; m_data = new float[m_rows * m_cols];
memcpy(m_data, s.m_data, sizeof(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) { 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_rows = 0;
s.m_cols = 0; s.m_cols = 0;
s.m_data = nullptr; s.m_data = nullptr;
} }
~Matrix() { ~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; delete[] m_data;
} }
Matrix &operator=(const Matrix &s) { 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; delete[] m_data;
m_rows = s.m_rows; m_rows = s.m_rows;
m_cols = s.m_cols; m_cols = s.m_cols;
@ -46,7 +47,7 @@ public:
} }
Matrix &operator=(Matrix &&s) { 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) { if (&s != this) {
delete[] m_data; delete[] m_data;
m_rows = s.m_rows; m_cols = s.m_cols; m_data = s.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) * m.rows(), /* Strides (in bytes) for each index */
sizeof(float) } sizeof(float) }
); );
}); })
;
} }

View File

@ -30,3 +30,16 @@ for i in range(m4.rows()):
for j in range(m4.cols()): for j in range(m4.cols()):
print(m4[i, j], end = ' ') print(m4[i, j], end = ' ')
print() 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)

View File

@ -1,4 +1,4 @@
Value constructor: Creating a 5x5 matrix ### Matrix @ 0x1df1920 created 5x5 matrix
0.0 0.0
4.0 4.0
[[ 0. 0. 0. 0. 0.] [[ 0. 0. 0. 0. 0.]
@ -10,8 +10,15 @@ Value constructor: Creating a 5x5 matrix
5.0 5.0
[[ 1. 2. 3.] [[ 1. 2. 3.]
[ 4. 5. 6.]] [ 4. 5. 6.]]
Value constructor: Creating a 2x3 matrix ### Matrix @ 0x1fa8cf0 created 2x3 matrix
1.0 2.0 3.0 1.0 2.0 3.0
4.0 5.0 6.0 4.0 5.0 6.0
Freeing a 2x3 matrix Instances not destroyed: 2
Freeing a 5x5 matrix ### 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

View File

@ -8,6 +8,7 @@
*/ */
#include "example.h" #include "example.h"
#include "constructor-stats.h"
#include <pybind11/functional.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) { void init_ex_callbacks(py::module &m) {
m.def("test_callback1", &test_callback1); m.def("test_callback1", &test_callback1);
m.def("test_callback2", &test_callback2); m.def("test_callback2", &test_callback2);
@ -66,21 +82,6 @@ void init_ex_callbacks(py::module &m) {
/* Test cleanup of lambda closure */ /* 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)> { m.def("test_cleanup", []() -> std::function<void(void)> {
Payload p; Payload p;
@ -94,4 +95,6 @@ void init_ex_callbacks(py::module &m) {
m.def("dummy_function2", &dummy_function2); m.def("dummy_function2", &dummy_function2);
m.def("roundtrip", &roundtrip); m.def("roundtrip", &roundtrip);
m.def("test_dummy_function", &test_dummy_function); m.def("test_dummy_function", &test_dummy_function);
// Export the payload constructor statistics for testing purposes:
m.def("payload_cstats", &ConstructorStats::get<Payload>);
} }

View File

@ -55,6 +55,12 @@ print("func(number=43) = %i" % f(number=43))
test_cleanup() 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_function
from example import dummy_function2 from example import dummy_function2
from example import test_dummy_function from example import test_dummy_function

View File

@ -7,7 +7,7 @@ Molly is a dog
Woof! Woof!
The following error is expected: Incompatible function arguments. The following argument types are supported: The following error is expected: Incompatible function arguments. The following argument types are supported:
1. (arg0: example.Dog) -> None 1. (arg0: example.Dog) -> None
Invoked with: <example.Pet object at 0> Invoked with: <example.Pet object at 0x7ffaf4b00db0>
Callback function 1 called! Callback function 1 called!
False False
Callback function 2 called : Hello, x, True, 5 Callback function 2 called : Hello, x, True, 5
@ -19,12 +19,15 @@ False
func(43) = 44 func(43) = 44
func(43) = 44 func(43) = 44
func(number=43) = 44 func(number=43) = 44
Payload constructor ### Payload @ 0x7ffdcee09c80 created via default constructor
Payload copy constructor ### Payload @ 0x7ffdcee09c88 created via copy constructor
Payload move constructor ### Payload @ 0xb54500 created via move constructor
Payload destructor ### Payload @ 0x7ffdcee09c88 destroyed
Payload destructor ### Payload @ 0x7ffdcee09c80 destroyed
Payload destructor ### Payload @ 0xb54500 destroyed
Payload instances not destroyed: 0
Copy constructions: 1
Move constructions: True
argument matches dummy_function argument matches dummy_function
eval(1) = 2 eval(1) = 2
roundtrip.. roundtrip..
@ -36,6 +39,7 @@ could not convert to a function pointer.
All OK! All OK!
could not convert to a function pointer. could not convert to a function pointer.
All OK! All OK!
test_callback3(arg0: Callable[[int], int]) -> None test_callback3(arg0: Callable[[int], int]) -> None
test_callback4() -> Callable[[int], int] test_callback4() -> Callable[[int], int]

View File

@ -8,32 +8,25 @@
BSD-style license that can be found in the LICENSE file. BSD-style license that can be found in the LICENSE file.
*/ */
#include <unordered_map>
#include <list>
#include "example.h" #include "example.h"
#include "constructor-stats.h"
class ExampleMandA { class ExampleMandA {
public: public:
ExampleMandA() { ExampleMandA() { print_default_created(this); }
cout << "Called ExampleMandA default constructor.." << endl; ExampleMandA(int value) : value(value) { print_created(this, value); }
} ExampleMandA(const ExampleMandA &e) : value(e.value) { print_copy_created(this); }
ExampleMandA(int value) : value(value) { ExampleMandA(ExampleMandA &&e) : value(e.value) { print_move_created(this); }
cout << "Called ExampleMandA constructor with value " << value << ".." << endl; ~ExampleMandA() { print_destroyed(this); }
}
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;
}
std::string toString() { std::string toString() {
return "ExampleMandA[value=" + std::to_string(value) + "]"; return "ExampleMandA[value=" + std::to_string(value) + "]";
} }
void operator=(const ExampleMandA &e) { cout << "Assignment operator" << endl; value = e.value; } void operator=(const ExampleMandA &e) { print_copy_assigned(this); value = e.value; }
void operator=(ExampleMandA &&e) { cout << "Move assignment operator" << endl; value = e.value; e.value = 0;} void operator=(ExampleMandA &&e) { print_move_assigned(this); value = e.value; }
void add1(ExampleMandA other) { value += other.value; } // passing by value void add1(ExampleMandA other) { value += other.value; } // passing by value
void add2(ExampleMandA &other) { value += other.value; } // passing by reference 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("internal4", &ExampleMandA::internal4)
.def("internal5", &ExampleMandA::internal5) .def("internal5", &ExampleMandA::internal5)
.def("__str__", &ExampleMandA::toString) .def("__str__", &ExampleMandA::toString)
.def_readwrite("value", &ExampleMandA::value); .def_readwrite("value", &ExampleMandA::value)
;
} }

View File

@ -35,3 +35,16 @@ print(instance1.internal5())
print("Instance 1, direct access = %i" % instance1.value) print("Instance 1, direct access = %i" % instance1.value)
instance1.value = 100 instance1.value = 100
print("Instance 1: " + str(instance1)) 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)

View File

@ -1,16 +1,16 @@
Called ExampleMandA default constructor.. ### ExampleMandA @ 0x2801910 created via default constructor
Called ExampleMandA constructor with value 32.. ### ExampleMandA @ 0x27fa780 created 32
Called ExampleMandA copy constructor with value 32.. ### ExampleMandA @ 0x7fff80a98a74 created via copy constructor
Called ExampleMandA copy constructor with value 32.. ### ExampleMandA @ 0x7fff80a98a78 created via copy constructor
Called ExampleMandA destructor (32) ### ExampleMandA @ 0x7fff80a98a78 destroyed
Called ExampleMandA destructor (32) ### ExampleMandA @ 0x7fff80a98a74 destroyed
Instance 1: ExampleMandA[value=320] Instance 1: ExampleMandA[value=320]
Instance 2: ExampleMandA[value=32] Instance 2: ExampleMandA[value=32]
Called ExampleMandA copy constructor with value 320.. ### ExampleMandA @ 0x7fff80a98a84 created via copy constructor
Called ExampleMandA move constructor with value 320.. ### ExampleMandA @ 0x2801fd0 created via move constructor
Called ExampleMandA destructor (0) ### ExampleMandA @ 0x7fff80a98a84 destroyed
ExampleMandA[value=320] ExampleMandA[value=320]
Called ExampleMandA destructor (320) ### ExampleMandA @ 0x2801fd0 destroyed
ExampleMandA[value=320] ExampleMandA[value=320]
ExampleMandA[value=320] ExampleMandA[value=320]
ExampleMandA[value=320] ExampleMandA[value=320]
@ -22,5 +22,13 @@ ExampleMandA[value=320]
320 320
Instance 1, direct access = 320 Instance 1, direct access = 320
Instance 1: ExampleMandA[value=100] Instance 1: ExampleMandA[value=100]
Called ExampleMandA destructor (32) Instances not destroyed: 2
Called ExampleMandA destructor (100) ### 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

View File

@ -9,6 +9,7 @@
*/ */
#include "example.h" #include "example.h"
#include "constructor-stats.h"
void submodule_func() { void submodule_func() {
std::cout << "submodule_func()" << std::endl; std::cout << "submodule_func()" << std::endl;
@ -16,9 +17,10 @@ void submodule_func() {
class A { class A {
public: public:
A(int v) : v(v) { std::cout << "A constructor" << std::endl; } A(int v) : v(v) { print_created(this, v); }
~A() { std::cout << "A destructor" << std::endl; } ~A() { print_destroyed(this); }
A(const A&) { std::cout << "A copy constructor" << std::endl; } 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) + "]"; } std::string toString() { return "A[" + std::to_string(v) + "]"; }
private: private:
int v; int v;
@ -26,9 +28,10 @@ private:
class B { class B {
public: public:
B() { std::cout << "B constructor" << std::endl; } B() { print_default_created(this); }
~B() { std::cout << "B destructor" << std::endl; } ~B() { print_destroyed(this); }
B(const B&) { std::cout << "B copy constructor" << std::endl; } 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_a1() { return a1; }
A &get_a2() { return a2; } A &get_a2() { return a2; }

View File

@ -28,3 +28,16 @@ print(b.get_a2())
print(b.a2) print(b.a2)
print(OD([(1, 'a'), (2, 'b')])) 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])

View File

@ -1,22 +1,31 @@
example example
example.submodule example.submodule
submodule_func() submodule_func()
A constructor ### A @ 0x21a5bc0 created 1
A constructor ### A @ 0x21a5bc4 created 2
B constructor ### B @ 0x21a5bc0 created via default constructor
A[1] A[1]
A[1] A[1]
A[2] A[2]
A[2] A[2]
A constructor ### A @ 0x20f93b0 created 42
A destructor ### A @ 0x21a5bc0 assigned via copy assignment
A constructor ### A @ 0x20f93b0 destroyed
A destructor ### A @ 0x20f93d0 created 43
### A @ 0x21a5bc4 assigned via copy assignment
### A @ 0x20f93d0 destroyed
A[42] A[42]
A[42] A[42]
A[43] A[43]
A[43] A[43]
OrderedDict([(1, 'a'), (2, 'b')]) OrderedDict([(1, 'a'), (2, 'b')])
B destructor Instances not destroyed: [2, 1]
A destructor ### B @ 0x21a5bc0 destroyed
A destructor ### 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]

View File

@ -34,6 +34,8 @@ print_opaque_list(cvp.stringList)
print_void_ptr(return_void_ptr()) print_void_ptr(return_void_ptr())
print_void_ptr(ExampleMandA()) # Should also work for other C++ types print_void_ptr(ExampleMandA()) # Should also work for other C++ types
from example import ConstructorStats
print("ExampleMandA still alive:", ConstructorStats.get(ExampleMandA).alive())
try: try:
print_void_ptr([1, 2, 3]) # This should not work print_void_ptr([1, 2, 3]) # This should not work

View File

@ -6,13 +6,14 @@ Opaque list: [Element 1]
Opaque list: [] Opaque list: []
Opaque list: [Element 1, Element 3] Opaque list: [Element 1, Element 3]
Got void ptr : 0x1234 Got void ptr : 0x1234
Called ExampleMandA default constructor.. ### ExampleMandA @ 0x2ac5370 created via default constructor
Got void ptr : 0x7f9ba0f3c430 Got void ptr : 0x2ac5370
Called ExampleMandA destructor (0) ### ExampleMandA @ 0x2ac5370 destroyed
ExampleMandA still alive: 0
Caught expected exception: Incompatible function arguments. The following argument types are supported: Caught expected exception: Incompatible function arguments. The following argument types are supported:
1. (arg0: capsule) -> None 1. (arg0: capsule) -> None
Invoked with: [1, 2, 3] Invoked with: [1, 2, 3]
None None
Got null str : 0x0 Got null str : 0x0
<example.StringList object at 0x10d3277a0> <example.StringList object at 0x7f7ecde6fc00>
Opaque list: [some value] Opaque list: [some value]

View File

@ -8,27 +8,28 @@
*/ */
#include "example.h" #include "example.h"
#include "constructor-stats.h"
#include <pybind11/operators.h> #include <pybind11/operators.h>
class Vector2 { class Vector2 {
public: public:
Vector2(float x, float y) : x(x), y(y) { std::cout << "Value constructor" << 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) { std::cout << "Copy constructor" << std::endl; } Vector2(const Vector2 &v) : x(v.x), y(v.y) { print_copy_created(this); }
Vector2(Vector2 &&v) : x(v.x), y(v.y) { std::cout << "Move constructor" << std::endl; v.x = v.y = 0; } Vector2(Vector2 &&v) : x(v.x), y(v.y) { print_move_created(this); v.x = v.y = 0; }
~Vector2() { std::cout << "Destructor." << std::endl; } ~Vector2() { print_destroyed(this); }
std::string toString() const { std::string toString() const {
return "[" + std::to_string(x) + ", " + std::to_string(y) + "]"; return "[" + std::to_string(x) + ", " + std::to_string(y) + "]";
} }
void operator=(const Vector2 &v) { void operator=(const Vector2 &v) {
cout << "Assignment operator" << endl; print_copy_assigned(this);
x = v.x; x = v.x;
y = v.y; y = v.y;
} }
void operator=(Vector2 &&v) { void operator=(Vector2 &&v) {
cout << "Move assignment operator" << endl; print_move_assigned(this);
x = v.x; y = v.y; v.x = v.y = 0; x = v.x; y = v.y; v.x = v.y = 0;
} }
@ -51,7 +52,6 @@ private:
float x, y; float x, y;
}; };
void init_ex_operator_overloading(py::module &m) { void init_ex_operator_overloading(py::module &m) {
py::class_<Vector2>(m, "Vector2") py::class_<Vector2>(m, "Vector2")
.def(py::init<float, float>()) .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(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"); m.attr("Vector") = m.attr("Vector2");
} }

View File

@ -25,3 +25,17 @@ v1 += v2
v1 *= 2 v1 *= 2
print("(v1+v2)*2 = " + str(v1)) 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)

View File

@ -1,57 +1,66 @@
Value constructor ### Vector2 @ 0x11f7830 created [1.000000, 2.000000]
Value constructor ### Vector2 @ 0x11427c0 created [3.000000, -1.000000]
v1 = [1.000000, 2.000000] v1 = [1.000000, 2.000000]
v2 = [3.000000, -1.000000] v2 = [3.000000, -1.000000]
Value constructor ### Vector2 @ 0x7ffef6b144b8 created [4.000000, 1.000000]
Move constructor ### Vector2 @ 0x11f7e90 created via move constructor
Destructor. ### Vector2 @ 0x7ffef6b144b8 destroyed
Destructor. ### Vector2 @ 0x11f7e90 destroyed
v1+v2 = [4.000000, 1.000000] v1+v2 = [4.000000, 1.000000]
Value constructor ### Vector2 @ 0x7ffef6b144b8 created [-2.000000, 3.000000]
Move constructor ### Vector2 @ 0x11f7e90 created via move constructor
Destructor. ### Vector2 @ 0x7ffef6b144b8 destroyed
Destructor. ### Vector2 @ 0x11f7e90 destroyed
v1-v2 = [-2.000000, 3.000000] v1-v2 = [-2.000000, 3.000000]
Value constructor ### Vector2 @ 0x7ffef6b144c8 created [-7.000000, -6.000000]
Move constructor ### Vector2 @ 0x1115760 created via move constructor
Destructor. ### Vector2 @ 0x7ffef6b144c8 destroyed
Destructor. ### Vector2 @ 0x1115760 destroyed
v1-8 = [-7.000000, -6.000000] v1-8 = [-7.000000, -6.000000]
Value constructor ### Vector2 @ 0x7ffef6b144c8 created [9.000000, 10.000000]
Move constructor ### Vector2 @ 0x1115760 created via move constructor
Destructor. ### Vector2 @ 0x7ffef6b144c8 destroyed
Destructor. ### Vector2 @ 0x1115760 destroyed
v1+8 = [9.000000, 10.000000] v1+8 = [9.000000, 10.000000]
Value constructor ### Vector2 @ 0x7ffef6b144b8 created [8.000000, 16.000000]
Move constructor ### Vector2 @ 0x1115760 created via move constructor
Destructor. ### Vector2 @ 0x7ffef6b144b8 destroyed
Destructor. ### Vector2 @ 0x1115760 destroyed
v1*8 = [8.000000, 16.000000] v1*8 = [8.000000, 16.000000]
Value constructor ### Vector2 @ 0x7ffef6b144a8 created [0.125000, 0.250000]
Move constructor ### Vector2 @ 0x112f150 created via move constructor
Destructor. ### Vector2 @ 0x7ffef6b144a8 destroyed
Destructor. ### Vector2 @ 0x112f150 destroyed
v1/8 = [0.125000, 0.250000] v1/8 = [0.125000, 0.250000]
Value constructor ### Vector2 @ 0x7ffef6b144f8 created [7.000000, 6.000000]
Move constructor ### Vector2 @ 0x112f1b0 created via move constructor
Destructor. ### Vector2 @ 0x7ffef6b144f8 destroyed
Destructor. ### Vector2 @ 0x112f1b0 destroyed
8-v1 = [7.000000, 6.000000] 8-v1 = [7.000000, 6.000000]
Value constructor ### Vector2 @ 0x7ffef6b144f8 created [9.000000, 10.000000]
Move constructor ### Vector2 @ 0x112f1b0 created via move constructor
Destructor. ### Vector2 @ 0x7ffef6b144f8 destroyed
Destructor. ### Vector2 @ 0x112f1b0 destroyed
8+v1 = [9.000000, 10.000000] 8+v1 = [9.000000, 10.000000]
Value constructor ### Vector2 @ 0x7ffef6b144e8 created [8.000000, 16.000000]
Move constructor ### Vector2 @ 0x112f230 created via move constructor
Destructor. ### Vector2 @ 0x7ffef6b144e8 destroyed
Destructor. ### Vector2 @ 0x112f230 destroyed
8*v1 = [8.000000, 16.000000] 8*v1 = [8.000000, 16.000000]
Value constructor ### Vector2 @ 0x7ffef6b144d8 created [8.000000, 4.000000]
Move constructor ### Vector2 @ 0x11fb360 created via move constructor
Destructor. ### Vector2 @ 0x7ffef6b144d8 destroyed
Destructor. ### Vector2 @ 0x11fb360 destroyed
8/v1 = [8.000000, 4.000000] 8/v1 = [8.000000, 4.000000]
(v1+v2)*2 = [8.000000, 2.000000] (v1+v2)*2 = [8.000000, 2.000000]
Destructor. Instances not destroyed: 2
Destructor. ### 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

View File

@ -9,6 +9,7 @@
*/ */
#include "example.h" #include "example.h"
#include "constructor-stats.h"
#include <pybind11/stl.h> #include <pybind11/stl.h>
#ifdef _WIN32 #ifdef _WIN32
@ -19,11 +20,11 @@
class ExamplePythonTypes { class ExamplePythonTypes {
public: public:
static ExamplePythonTypes *new_instance() { static ExamplePythonTypes *new_instance() {
return new ExamplePythonTypes(); auto *ptr = new ExamplePythonTypes();
} print_created(ptr, "via new_instance");
~ExamplePythonTypes() { return ptr;
std::cout << "Destructing ExamplePythonTypes" << std::endl;
} }
~ExamplePythonTypes() { print_destroyed(this); }
/* Create and return a Python dictionary */ /* Create and return a Python dictionary */
py::dict get_dict() { 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("throw_exception", &ExamplePythonTypes::throw_exception, "Throw an exception")
.def_static("new_instance", &ExamplePythonTypes::new_instance, "Return an instance") .def_static("new_instance", &ExamplePythonTypes::new_instance, "Return an instance")
.def_readwrite_static("value", &ExamplePythonTypes::value, "Static value member") .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)")
;
} }

View File

@ -65,3 +65,10 @@ print("__name__(example.ExamplePythonTypes) = %s" % ExamplePythonTypes.__name__)
print("__module__(example.ExamplePythonTypes) = %s" % ExamplePythonTypes.__module__) print("__module__(example.ExamplePythonTypes) = %s" % ExamplePythonTypes.__module__)
print("__name__(example.ExamplePythonTypes.get_set) = %s" % ExamplePythonTypes.get_set.__name__) print("__name__(example.ExamplePythonTypes.get_set) = %s" % ExamplePythonTypes.get_set.__name__)
print("__module__(example.ExamplePythonTypes.get_set) = %s" % ExamplePythonTypes.get_set.__module__) print("__module__(example.ExamplePythonTypes.get_set) = %s" % ExamplePythonTypes.get_set.__module__)
from example import ConstructorStats
cstats = ConstructorStats.get(ExamplePythonTypes)
print("Instances not destroyed:", cstats.alive())
instance = None
print("Instances not destroyed:", cstats.alive())

View File

@ -2,6 +2,7 @@
5 5
example.ExamplePythonTypes: No constructor defined! example.ExamplePythonTypes: No constructor defined!
can't set attribute can't set attribute
### ExamplePythonTypes @ 0x1045b80 created via new_instance
key: key2, value=value2 key: key2, value=value2
key: key, value=value key: key, value=value
key: key, value=value key: key, value=value
@ -134,4 +135,6 @@ __name__(example.ExamplePythonTypes) = ExamplePythonTypes
__module__(example.ExamplePythonTypes) = example __module__(example.ExamplePythonTypes) = example
__name__(example.ExamplePythonTypes.get_set) = get_set __name__(example.ExamplePythonTypes.get_set) = get_set
__module__(example.ExamplePythonTypes.get_set) = example __module__(example.ExamplePythonTypes.get_set) = example
Destructing ExamplePythonTypes Instances not destroyed: 1
### ExamplePythonTypes @ 0x1045b80 destroyed
Instances not destroyed: 0

View File

@ -9,51 +9,55 @@
*/ */
#include "example.h" #include "example.h"
#include "constructor-stats.h"
#include <pybind11/operators.h> #include <pybind11/operators.h>
#include <pybind11/stl.h> #include <pybind11/stl.h>
class Sequence { class Sequence {
public: public:
Sequence(size_t size) : m_size(size) { Sequence(size_t size) : m_size(size) {
std::cout << "Value constructor: Creating a sequence with " << m_size << " entries" << std::endl; print_created(this, "of size", m_size);
m_data = new float[size]; m_data = new float[size];
memset(m_data, 0, sizeof(float) * size); memset(m_data, 0, sizeof(float) * size);
} }
Sequence(const std::vector<float> &value) : m_size(value.size()) { Sequence(const std::vector<float> &value) : m_size(value.size()) {
std::cout << "Value constructor: Creating a sequence with " << m_size << " entries" << std::endl; print_created(this, "of size", m_size, "from std::vector");
m_data = new float[m_size]; m_data = new float[m_size];
memcpy(m_data, &value[0], sizeof(float) * m_size); memcpy(m_data, &value[0], sizeof(float) * m_size);
} }
Sequence(const Sequence &s) : m_size(s.m_size) { Sequence(const Sequence &s) : m_size(s.m_size) {
std::cout << "Copy constructor: Creating a sequence with " << m_size << " entries" << std::endl; print_copy_created(this);
m_data = new float[m_size]; m_data = new float[m_size];
memcpy(m_data, s.m_data, sizeof(float)*m_size); memcpy(m_data, s.m_data, sizeof(float)*m_size);
} }
Sequence(Sequence &&s) : m_size(s.m_size), m_data(s.m_data) { Sequence(Sequence &&s) : m_size(s.m_size), m_data(s.m_data) {
std::cout << "Move constructor: Creating a sequence with " << m_size << " entries" << std::endl; print_move_created(this);
s.m_size = 0; s.m_size = 0;
s.m_data = nullptr; s.m_data = nullptr;
} }
~Sequence() { ~Sequence() {
std::cout << "Freeing a sequence with " << m_size << " entries" << std::endl; print_destroyed(this);
delete[] m_data; delete[] m_data;
} }
Sequence &operator=(const Sequence &s) { Sequence &operator=(const Sequence &s) {
std::cout << "Assignment operator: Creating a sequence with " << s.m_size << " entries" << std::endl; if (&s != this) {
delete[] m_data; delete[] m_data;
m_size = s.m_size; m_size = s.m_size;
m_data = new float[m_size]; m_data = new float[m_size];
memcpy(m_data, s.m_data, sizeof(float)*m_size); memcpy(m_data, s.m_data, sizeof(float)*m_size);
}
print_copy_assigned(this);
return *this; return *this;
} }
Sequence &operator=(Sequence &&s) { Sequence &operator=(Sequence &&s) {
std::cout << "Move assignment operator: Creating a sequence with " << s.m_size << " entries" << std::endl;
if (&s != this) { if (&s != this) {
delete[] m_data; delete[] m_data;
m_size = s.m_size; m_size = s.m_size;
@ -61,6 +65,9 @@ public:
s.m_size = 0; s.m_size = 0;
s.m_data = nullptr; s.m_data = nullptr;
} }
print_move_assigned(this);
return *this; return *this;
} }

View File

@ -28,3 +28,19 @@ rev[0::2] = Sequence([2.0, 2.0, 2.0])
for i in rev: for i in rev:
print(i, end=' ') print(i, end=' ')
print('') print('')
from example import ConstructorStats
cstats = ConstructorStats.get(Sequence)
print("Instances not destroyed:", cstats.alive())
s = None
print("Instances not destroyed:", cstats.alive())
rev = None
print("Instances not destroyed:", cstats.alive())
rev2 = 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)

View File

@ -1,21 +1,31 @@
Value constructor: Creating a sequence with 5 entries ### Sequence @ 0x1535b00 created of size 5
s = <example.Sequence object at 0x10c786c70> s = <example.Sequence object at 0x7efc73cfa4e0>
len(s) = 5 len(s) = 5
s[0], s[3] = 0.000000 0.000000 s[0], s[3] = 0.000000 0.000000
12.34 in s: False 12.34 in s: False
12.34 in s: True 12.34 in s: True
s[0], s[3] = 12.340000 56.779999 s[0], s[3] = 12.340000 56.779999
Value constructor: Creating a sequence with 5 entries ### Sequence @ 0x7fff22a45068 created of size 5
Move constructor: Creating a sequence with 5 entries ### Sequence @ 0x1538b90 created via move constructor
Freeing a sequence with 0 entries ### Sequence @ 0x7fff22a45068 destroyed
Value constructor: Creating a sequence with 5 entries ### Sequence @ 0x1538bf0 created of size 5
rev[0], rev[1], rev[2], rev[3], rev[4] = 0.000000 56.779999 0.000000 0.000000 12.340000 rev[0], rev[1], rev[2], rev[3], rev[4] = 0.000000 56.779999 0.000000 0.000000 12.340000
0.0 56.7799987793 0.0 0.0 12.3400001526 0.0 56.779998779296875 0.0 0.0 12.34000015258789
0.0 56.7799987793 0.0 0.0 12.3400001526 0.0 56.779998779296875 0.0 0.0 12.34000015258789
True True
Value constructor: Creating a sequence with 3 entries ### Sequence @ 0x153c4b0 created of size 3 from std::vector
Freeing a sequence with 3 entries ### Sequence @ 0x153c4b0 destroyed
2.0 56.7799987793 2.0 0.0 2.0 2.0 56.779998779296875 2.0 0.0 2.0
Freeing a sequence with 5 entries Instances not destroyed: 3
Freeing a sequence with 5 entries ### Sequence @ 0x1535b00 destroyed
Freeing a sequence with 5 entries Instances not destroyed: 2
### Sequence @ 0x1538b90 destroyed
Instances not destroyed: 1
### Sequence @ 0x1538bf0 destroyed
Instances not destroyed: 0
Constructor values: ['of size', '5', 'of size', '5', 'of size', '5', 'of size', '3', 'from std::vector']
Default constructions: 0
Copy constructions: 0
Move constructions: True
Copy assignments: 0
Move assignments: 0

View File

@ -15,7 +15,7 @@
class MyObject1 : public Object { class MyObject1 : public Object {
public: public:
MyObject1(int value) : value(value) { MyObject1(int value) : value(value) {
std::cout << toString() << " constructor" << std::endl; print_created(this, toString());
} }
std::string toString() const { std::string toString() const {
@ -24,7 +24,7 @@ public:
protected: protected:
virtual ~MyObject1() { virtual ~MyObject1() {
std::cout << toString() << " destructor" << std::endl; print_destroyed(this);
} }
private: private:
@ -35,7 +35,7 @@ private:
class MyObject2 { class MyObject2 {
public: public:
MyObject2(int value) : value(value) { MyObject2(int value) : value(value) {
std::cout << toString() << " constructor" << std::endl; print_created(this, toString());
} }
std::string toString() const { std::string toString() const {
@ -43,7 +43,7 @@ public:
} }
virtual ~MyObject2() { virtual ~MyObject2() {
std::cout << toString() << " destructor" << std::endl; print_destroyed(this);
} }
private: private:
@ -54,7 +54,7 @@ private:
class MyObject3 : public std::enable_shared_from_this<MyObject3> { class MyObject3 : public std::enable_shared_from_this<MyObject3> {
public: public:
MyObject3(int value) : value(value) { MyObject3(int value) : value(value) {
std::cout << toString() << " constructor" << std::endl; print_created(this, toString());
} }
std::string toString() const { std::string toString() const {
@ -62,7 +62,7 @@ public:
} }
virtual ~MyObject3() { virtual ~MyObject3() {
std::cout << toString() << " destructor" << std::endl; print_destroyed(this);
} }
private: private:
@ -144,4 +144,7 @@ void init_ex_smart_ptr(py::module &m) {
m.def("print_myobject3_4", &print_myobject3_4); m.def("print_myobject3_4", &print_myobject3_4);
py::implicitly_convertible<py::int_, MyObject1>(); py::implicitly_convertible<py::int_, MyObject1>();
// Expose constructor stats for the ref type
m.def("cstats_ref", &ConstructorStats::get<ref_tag>);
} }

View File

@ -68,3 +68,18 @@ for o in [MyObject3(9), make_myobject3_1(), make_myobject3_2()]:
print_myobject3_2(o) print_myobject3_2(o)
print_myobject3_3(o) print_myobject3_3(o)
print_myobject3_4(o) print_myobject3_4(o)
from example import ConstructorStats, cstats_ref, Object
cstats = [ConstructorStats.get(Object), ConstructorStats.get(MyObject1),
ConstructorStats.get(MyObject2), ConstructorStats.get(MyObject3),
cstats_ref()]
print("Instances not destroyed:", [x.alive() for x in cstats])
o = None
print("Instances not destroyed:", [x.alive() for x in cstats])
print("Object value constructions:", [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]) # Doesn't invoke any
print("Copy assignments:", [x.copy_assignments for x in cstats])
print("Move assignments:", [x.move_assignments for x in cstats])

View File

@ -1,243 +1,270 @@
MyObject1[1] constructor ### Object @ 0xdeffd0 created via default constructor
Initialized ref from pointer 0x1347ba0 ### MyObject1 @ 0xdeffd0 created MyObject1[1]
MyObject1[2] constructor ### ref<MyObject1> @ 0x7f6a2e03c4a8 created from pointer 0xdeffd0
Initialized ref from pointer 0x12b9270 ### Object @ 0xe43f50 created via default constructor
Initialized ref from ref 0x12b9270 ### MyObject1 @ 0xe43f50 created MyObject1[2]
Destructing ref 0x12b9270 ### ref<Object> @ 0x7fff136845d0 created from pointer 0xe43f50
MyObject1[3] constructor ### ref<MyObject1> @ 0x7f6a2c32aad8 created via copy constructor with pointer 0xe43f50
Initialized ref from pointer 0x12a2a90 ### ref<Object> @ 0x7fff136845d0 destroyed
MyObject1[1] ### Object @ 0xee8cf0 created via default constructor
Created empty ref ### MyObject1 @ 0xee8cf0 created MyObject1[3]
Assigning ref 0x1347ba0 ### ref<MyObject1> @ 0x7f6a2c32ab08 created from pointer 0xee8cf0
Initialized ref from ref 0x1347ba0
MyObject1[1]
Destructing ref 0x1347ba0
Destructing ref 0x1347ba0
Created empty ref
Assigning ref 0x1347ba0
MyObject1[1]
Destructing ref 0x1347ba0
Created empty ref
Assigning ref 0x1347ba0
MyObject1[1]
Destructing ref 0x1347ba0
MyObject1[2]
Created empty ref
Assigning ref 0x12b9270
Initialized ref from ref 0x12b9270
MyObject1[2]
Destructing ref 0x12b9270
Destructing ref 0x12b9270
Created empty ref
Assigning ref 0x12b9270
MyObject1[2]
Destructing ref 0x12b9270
Created empty ref
Assigning ref 0x12b9270
MyObject1[2]
Destructing ref 0x12b9270
MyObject1[3]
Created empty ref
Assigning ref 0x12a2a90
Initialized ref from ref 0x12a2a90
MyObject1[3]
Destructing ref 0x12a2a90
Destructing ref 0x12a2a90
Created empty ref
Assigning ref 0x12a2a90
MyObject1[3]
Destructing ref 0x12a2a90
Created empty ref
Assigning ref 0x12a2a90
MyObject1[3]
Destructing ref 0x12a2a90
Destructing ref 0x12b9270
MyObject1[2] destructor
Destructing ref 0x1347ba0
MyObject1[1] destructor
MyObject1[4] constructor
Initialized ref from pointer 0x1347ba0
MyObject1[5] constructor
Initialized ref from pointer 0x1299190
Initialized ref from ref 0x1299190
Destructing ref 0x1299190
MyObject1[6] constructor
Initialized ref from pointer 0x133e2f0
Destructing ref 0x12a2a90
MyObject1[3] destructor
MyObject1[4]
Created empty ref
Assigning ref 0x1347ba0
Initialized ref from ref 0x1347ba0
MyObject1[4]
Destructing ref 0x1347ba0
Destructing ref 0x1347ba0
Created empty ref
Assigning ref 0x1347ba0
MyObject1[4]
Destructing ref 0x1347ba0
Created empty ref
Assigning ref 0x1347ba0
MyObject1[4]
Destructing ref 0x1347ba0
MyObject1[4]
Created empty ref
Assigning ref 0x1347ba0
Initialized ref from ref 0x1347ba0
MyObject1[4]
Destructing ref 0x1347ba0
Destructing ref 0x1347ba0
Created empty ref
Assigning ref 0x1347ba0
MyObject1[4]
Destructing ref 0x1347ba0
Created empty ref
Assigning ref 0x1347ba0
MyObject1[4]
Destructing ref 0x1347ba0
MyObject1[5]
Created empty ref
Assigning ref 0x1299190
Initialized ref from ref 0x1299190
MyObject1[5]
Destructing ref 0x1299190
Destructing ref 0x1299190
Created empty ref
Assigning ref 0x1299190
MyObject1[5]
Destructing ref 0x1299190
Created empty ref
Assigning ref 0x1299190
MyObject1[5]
Destructing ref 0x1299190
MyObject1[5]
Created empty ref
Assigning ref 0x1299190
Initialized ref from ref 0x1299190
MyObject1[5]
Destructing ref 0x1299190
Destructing ref 0x1299190
Created empty ref
Assigning ref 0x1299190
MyObject1[5]
Destructing ref 0x1299190
Created empty ref
Assigning ref 0x1299190
MyObject1[5]
Destructing ref 0x1299190
MyObject1[6]
Created empty ref
Assigning ref 0x133e2f0
Initialized ref from ref 0x133e2f0
MyObject1[6]
Destructing ref 0x133e2f0
Destructing ref 0x133e2f0
Created empty ref
Assigning ref 0x133e2f0
MyObject1[6]
Destructing ref 0x133e2f0
Created empty ref
Assigning ref 0x133e2f0
MyObject1[6]
Destructing ref 0x133e2f0
MyObject1[6]
Created empty ref
Assigning ref 0x133e2f0
Initialized ref from ref 0x133e2f0
MyObject1[6]
Destructing ref 0x133e2f0
Destructing ref 0x133e2f0
Created empty ref
Assigning ref 0x133e2f0
MyObject1[6]
Destructing ref 0x133e2f0
Created empty ref
Assigning ref 0x133e2f0
MyObject1[6]
Destructing ref 0x133e2f0
MyObject1[7] constructor
Initialized ref from pointer 0x133f3a0
MyObject1[7]
Destructing ref 0x133f3a0
MyObject1[7] destructor
Created empty ref
MyObject1[7] constructor
Initialized ref from pointer 0x12a2a90
Assigning ref 0x12a2a90
Initialized ref from ref 0x12a2a90
MyObject1[7]
Destructing ref 0x12a2a90
Destructing ref 0x12a2a90
Destructing ref 0x12a2a90
MyObject1[7] destructor
Created empty ref
MyObject1[7] constructor
Initialized ref from pointer 0x133f3a0
Assigning ref 0x133f3a0
MyObject1[7]
Destructing ref 0x133f3a0
Destructing ref 0x133f3a0
MyObject1[7] destructor
Created empty ref
MyObject1[7] constructor
Initialized ref from pointer 0x12a2a90
Assigning ref 0x12a2a90
MyObject1[7]
Destructing ref 0x12a2a90
Destructing ref 0x12a2a90
MyObject1[7] destructor
Destructing ref 0x133e2f0
MyObject1[6] destructor
Destructing ref 0x1299190
MyObject1[5] destructor
Destructing ref 0x1347ba0
MyObject1[4] destructor
MyObject2[8] constructor
MyObject2[6] constructor
MyObject2[7] constructor
MyObject2[8]
MyObject2[8]
MyObject2[8]
MyObject2[8]
MyObject2[6]
MyObject2[6]
MyObject2[6]
MyObject2[6]
MyObject2[7]
MyObject2[7]
MyObject2[7]
MyObject2[7]
MyObject2[6] destructor
MyObject2[8] destructor
MyObject3[9] constructor
MyObject3[8] constructor
MyObject3[9] constructor
MyObject2[7] destructor
MyObject3[9]
MyObject3[9]
MyObject3[9]
MyObject3[9]
MyObject3[8]
MyObject3[8]
MyObject3[8]
MyObject3[8]
MyObject3[9]
MyObject3[9]
MyObject3[9]
MyObject3[9]
MyObject3[8] destructor
MyObject3[9] destructor
Reference count = 1 Reference count = 1
MyObject1[1]
### ref<Object> @ 0x7fff136845c8 created via default constructor
### ref<Object> @ 0x7fff136845c8 assigned via copy assignment pointer 0xdeffd0
### ref<Object> @ 0x7fff136845a8 created via copy constructor with pointer 0xdeffd0
MyObject1[1]
### ref<Object> @ 0x7fff136845a8 destroyed
### ref<Object> @ 0x7fff136845c8 destroyed
### ref<Object> @ 0x7fff136845c8 created via default constructor
### ref<Object> @ 0x7fff136845c8 assigned via copy assignment pointer 0xdeffd0
MyObject1[1]
### ref<Object> @ 0x7fff136845c8 destroyed
### ref<Object> @ 0x7fff136845c8 created via default constructor
### ref<Object> @ 0x7fff136845c8 assigned via copy assignment pointer 0xdeffd0
MyObject1[1]
### ref<Object> @ 0x7fff136845c8 destroyed
Reference count = 1 Reference count = 1
MyObject1[2]
### ref<Object> @ 0x7fff136845c8 created via default constructor
### ref<Object> @ 0x7fff136845c8 assigned via copy assignment pointer 0xe43f50
### ref<Object> @ 0x7fff136845a8 created via copy constructor with pointer 0xe43f50
MyObject1[2]
### ref<Object> @ 0x7fff136845a8 destroyed
### ref<Object> @ 0x7fff136845c8 destroyed
### ref<Object> @ 0x7fff136845c8 created via default constructor
### ref<Object> @ 0x7fff136845c8 assigned via copy assignment pointer 0xe43f50
MyObject1[2]
### ref<Object> @ 0x7fff136845c8 destroyed
### ref<Object> @ 0x7fff136845c8 created via default constructor
### ref<Object> @ 0x7fff136845c8 assigned via copy assignment pointer 0xe43f50
MyObject1[2]
### ref<Object> @ 0x7fff136845c8 destroyed
Reference count = 1 Reference count = 1
<example.MyObject1 object at 0x7f830b500e68> MyObject1[3]
<example.MyObject1 object at 0x7f830b4fc688> ### ref<Object> @ 0x7fff136845c8 created via default constructor
<example.MyObject1 object at 0x7f830b4fc5a8> ### ref<Object> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8cf0
### ref<Object> @ 0x7fff136845a8 created via copy constructor with pointer 0xee8cf0
MyObject1[3]
### ref<Object> @ 0x7fff136845a8 destroyed
### ref<Object> @ 0x7fff136845c8 destroyed
### ref<Object> @ 0x7fff136845c8 created via default constructor
### ref<Object> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8cf0
MyObject1[3]
### ref<Object> @ 0x7fff136845c8 destroyed
### ref<Object> @ 0x7fff136845c8 created via default constructor
### ref<Object> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8cf0
MyObject1[3]
### ref<Object> @ 0x7fff136845c8 destroyed
### MyObject1 @ 0xe43f50 destroyed
### Object @ 0xe43f50 destroyed
### ref<MyObject1> @ 0x7f6a2c32aad8 destroyed
### MyObject1 @ 0xdeffd0 destroyed
### Object @ 0xdeffd0 destroyed
### ref<MyObject1> @ 0x7f6a2e03c4a8 destroyed
### Object @ 0xee8310 created via default constructor
### MyObject1 @ 0xee8310 created MyObject1[4]
### ref<MyObject1> @ 0x7f6a2e03c4a8 created from pointer 0xee8310
### Object @ 0xee8470 created via default constructor
### MyObject1 @ 0xee8470 created MyObject1[5]
### ref<MyObject1> @ 0x7fff136845d0 created from pointer 0xee8470
### ref<MyObject1> @ 0x7f6a2c32aad8 created via copy constructor with pointer 0xee8470
### ref<MyObject1> @ 0x7fff136845d0 destroyed
### Object @ 0xee95a0 created via default constructor
### MyObject1 @ 0xee95a0 created MyObject1[6]
### ref<MyObject1> @ 0x7f6a2c32ab38 created from pointer 0xee95a0
### MyObject1 @ 0xee8cf0 destroyed
### Object @ 0xee8cf0 destroyed
### ref<MyObject1> @ 0x7f6a2c32ab08 destroyed
<example.MyObject1 object at 0x7f6a2e03c480>
MyObject1[4]
### ref<Object> @ 0x7fff136845c8 created via default constructor
### ref<Object> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8310
### ref<Object> @ 0x7fff136845a8 created via copy constructor with pointer 0xee8310
MyObject1[4]
### ref<Object> @ 0x7fff136845a8 destroyed
### ref<Object> @ 0x7fff136845c8 destroyed
### ref<Object> @ 0x7fff136845c8 created via default constructor
### ref<Object> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8310
MyObject1[4]
### ref<Object> @ 0x7fff136845c8 destroyed
### ref<Object> @ 0x7fff136845c8 created via default constructor
### ref<Object> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8310
MyObject1[4]
### ref<Object> @ 0x7fff136845c8 destroyed
MyObject1[4]
### ref<MyObject1> @ 0x7fff136845c8 created via default constructor
### ref<MyObject1> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8310
### ref<MyObject1> @ 0x7fff136845a8 created via copy constructor with pointer 0xee8310
MyObject1[4]
### ref<MyObject1> @ 0x7fff136845a8 destroyed
### ref<MyObject1> @ 0x7fff136845c8 destroyed
### ref<MyObject1> @ 0x7fff136845c8 created via default constructor
### ref<MyObject1> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8310
MyObject1[4]
### ref<MyObject1> @ 0x7fff136845c8 destroyed
### ref<MyObject1> @ 0x7fff136845c8 created via default constructor
### ref<MyObject1> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8310
MyObject1[4]
### ref<MyObject1> @ 0x7fff136845c8 destroyed
<example.MyObject1 object at 0x7f6a2c32aab0>
MyObject1[5]
### ref<Object> @ 0x7fff136845c8 created via default constructor
### ref<Object> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8470
### ref<Object> @ 0x7fff136845a8 created via copy constructor with pointer 0xee8470
MyObject1[5]
### ref<Object> @ 0x7fff136845a8 destroyed
### ref<Object> @ 0x7fff136845c8 destroyed
### ref<Object> @ 0x7fff136845c8 created via default constructor
### ref<Object> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8470
MyObject1[5]
### ref<Object> @ 0x7fff136845c8 destroyed
### ref<Object> @ 0x7fff136845c8 created via default constructor
### ref<Object> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8470
MyObject1[5]
### ref<Object> @ 0x7fff136845c8 destroyed
MyObject1[5]
### ref<MyObject1> @ 0x7fff136845c8 created via default constructor
### ref<MyObject1> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8470
### ref<MyObject1> @ 0x7fff136845a8 created via copy constructor with pointer 0xee8470
MyObject1[5]
### ref<MyObject1> @ 0x7fff136845a8 destroyed
### ref<MyObject1> @ 0x7fff136845c8 destroyed
### ref<MyObject1> @ 0x7fff136845c8 created via default constructor
### ref<MyObject1> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8470
MyObject1[5]
### ref<MyObject1> @ 0x7fff136845c8 destroyed
### ref<MyObject1> @ 0x7fff136845c8 created via default constructor
### ref<MyObject1> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8470
MyObject1[5]
### ref<MyObject1> @ 0x7fff136845c8 destroyed
<example.MyObject1 object at 0x7f6a2c32ab10>
MyObject1[6]
### ref<Object> @ 0x7fff136845c8 created via default constructor
### ref<Object> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee95a0
### ref<Object> @ 0x7fff136845a8 created via copy constructor with pointer 0xee95a0
MyObject1[6]
### ref<Object> @ 0x7fff136845a8 destroyed
### ref<Object> @ 0x7fff136845c8 destroyed
### ref<Object> @ 0x7fff136845c8 created via default constructor
### ref<Object> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee95a0
MyObject1[6]
### ref<Object> @ 0x7fff136845c8 destroyed
### ref<Object> @ 0x7fff136845c8 created via default constructor
### ref<Object> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee95a0
MyObject1[6]
### ref<Object> @ 0x7fff136845c8 destroyed
MyObject1[6]
### ref<MyObject1> @ 0x7fff136845c8 created via default constructor
### ref<MyObject1> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee95a0
### ref<MyObject1> @ 0x7fff136845a8 created via copy constructor with pointer 0xee95a0
MyObject1[6]
### ref<MyObject1> @ 0x7fff136845a8 destroyed
### ref<MyObject1> @ 0x7fff136845c8 destroyed
### ref<MyObject1> @ 0x7fff136845c8 created via default constructor
### ref<MyObject1> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee95a0
MyObject1[6]
### ref<MyObject1> @ 0x7fff136845c8 destroyed
### ref<MyObject1> @ 0x7fff136845c8 created via default constructor
### ref<MyObject1> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee95a0
MyObject1[6]
### ref<MyObject1> @ 0x7fff136845c8 destroyed
7 7
<example.MyObject2 object at 0x7f830b50b330> ### Object @ 0xee97f0 created via default constructor
<example.MyObject2 object at 0x7f830b50bdb0> ### MyObject1 @ 0xee97f0 created MyObject1[7]
<example.MyObject2 object at 0x7f83098f6330> ### ref<MyObject1> @ 0x7f6a2c32ab08 created from pointer 0xee97f0
<example.MyObject3 object at 0x7f830b50b330> MyObject1[7]
<example.MyObject3 object at 0x7f830b50bdb0> ### MyObject1 @ 0xee97f0 destroyed
<example.MyObject3 object at 0x7f83098f6370> ### Object @ 0xee97f0 destroyed
MyObject3[9] destructor ### ref<MyObject1> @ 0x7f6a2c32ab08 destroyed
### ref<MyObject1> @ 0x7fff136845c8 created via default constructor
### Object @ 0xee99e0 created via default constructor
### MyObject1 @ 0xee99e0 created MyObject1[7]
### ref<MyObject1> @ 0x7f6a2c32ab08 created from pointer 0xee99e0
### ref<MyObject1> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee99e0
### ref<MyObject1> @ 0x7fff136845a8 created via copy constructor with pointer 0xee99e0
MyObject1[7]
### ref<MyObject1> @ 0x7fff136845a8 destroyed
### ref<MyObject1> @ 0x7fff136845c8 destroyed
### MyObject1 @ 0xee99e0 destroyed
### Object @ 0xee99e0 destroyed
### ref<MyObject1> @ 0x7f6a2c32ab08 destroyed
### ref<MyObject1> @ 0x7fff136845c8 created via default constructor
### Object @ 0xee97f0 created via default constructor
### MyObject1 @ 0xee97f0 created MyObject1[7]
### ref<MyObject1> @ 0x7f6a2c32ab08 created from pointer 0xee97f0
### ref<MyObject1> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee97f0
MyObject1[7]
### ref<MyObject1> @ 0x7fff136845c8 destroyed
### MyObject1 @ 0xee97f0 destroyed
### Object @ 0xee97f0 destroyed
### ref<MyObject1> @ 0x7f6a2c32ab08 destroyed
### ref<MyObject1> @ 0x7fff136845c8 created via default constructor
### Object @ 0xee99e0 created via default constructor
### MyObject1 @ 0xee99e0 created MyObject1[7]
### ref<MyObject1> @ 0x7f6a2c32ab08 created from pointer 0xee99e0
### ref<MyObject1> @ 0x7fff136845c8 assigned via copy assignment pointer 0xee99e0
MyObject1[7]
### ref<MyObject1> @ 0x7fff136845c8 destroyed
### MyObject1 @ 0xee99e0 destroyed
### Object @ 0xee99e0 destroyed
### ref<MyObject1> @ 0x7f6a2c32ab08 destroyed
### MyObject1 @ 0xee95a0 destroyed
### Object @ 0xee95a0 destroyed
### ref<MyObject1> @ 0x7f6a2c32ab38 destroyed
### MyObject1 @ 0xee8470 destroyed
### Object @ 0xee8470 destroyed
### ref<MyObject1> @ 0x7f6a2c32aad8 destroyed
### MyObject1 @ 0xee8310 destroyed
### Object @ 0xee8310 destroyed
### ref<MyObject1> @ 0x7f6a2e03c4a8 destroyed
### MyObject2 @ 0xe43f50 created MyObject2[8]
### MyObject2 @ 0xee95a0 created MyObject2[6]
### MyObject2 @ 0xee95d0 created MyObject2[7]
<example.MyObject2 object at 0x7f6a2dfc8768>
MyObject2[8]
MyObject2[8]
MyObject2[8]
MyObject2[8]
<example.MyObject2 object at 0x7f6a2dfc86c0>
MyObject2[6]
MyObject2[6]
MyObject2[6]
MyObject2[6]
<example.MyObject2 object at 0x7f6a2c32d030>
MyObject2[7]
MyObject2[7]
MyObject2[7]
MyObject2[7]
### MyObject2 @ 0xee95a0 destroyed
### MyObject2 @ 0xe43f50 destroyed
### MyObject3 @ 0xee9ac0 created MyObject3[9]
### MyObject3 @ 0xe43f90 created MyObject3[8]
### MyObject3 @ 0xeea7d0 created MyObject3[9]
### MyObject2 @ 0xee95d0 destroyed
<example.MyObject3 object at 0x7f6a2dfc8768>
MyObject3[9]
MyObject3[9]
MyObject3[9]
MyObject3[9]
<example.MyObject3 object at 0x7f6a2dfc86c0>
MyObject3[8]
MyObject3[8]
MyObject3[8]
MyObject3[8]
<example.MyObject3 object at 0x7f6a2c32d068>
MyObject3[9]
MyObject3[9]
MyObject3[9]
MyObject3[9]
### MyObject3 @ 0xe43f90 destroyed
### MyObject3 @ 0xee9ac0 destroyed
Instances not destroyed: [0, 0, 0, 1, 0]
### MyObject3 @ 0xeea7d0 destroyed
Instances not destroyed: [0, 0, 0, 0, 0]
Object value constructions: [[], ['MyObject1[1]', 'MyObject1[2]', 'MyObject1[3]', 'MyObject1[4]', 'MyObject1[5]', 'MyObject1[6]', 'MyObject1[7]', 'MyObject1[7]', 'MyObject1[7]', 'MyObject1[7]'], ['MyObject2[8]', 'MyObject2[6]', 'MyObject2[7]'], ['MyObject3[9]', 'MyObject3[8]', 'MyObject3[9]'], ['from pointer', 'from pointer', 'from pointer', 'from pointer', 'from pointer', 'from pointer', 'from pointer', 'from pointer', 'from pointer', 'from pointer']]
Default constructions: [10, 0, 0, 0, 30]
Copy constructions: [0, 0, 0, 0, 12]
Copy assignments: [0, 0, 0, 0, 30]
Move assignments: [0, 0, 0, 0, 0]

View File

@ -8,18 +8,16 @@
*/ */
#include "example.h" #include "example.h"
#include "constructor-stats.h"
#include <pybind11/functional.h> #include <pybind11/functional.h>
/* This is an example class that we'll want to be able to extend from Python */ /* This is an example class that we'll want to be able to extend from Python */
class ExampleVirt { class ExampleVirt {
public: public:
ExampleVirt(int state) : state(state) { ExampleVirt(int state) : state(state) { print_created(this, state); }
cout << "Constructing ExampleVirt.." << endl; ExampleVirt(const ExampleVirt &e) : state(e.state) { print_copy_created(this); }
} ExampleVirt(ExampleVirt &&e) : state(e.state) { print_move_created(this); e.state = 0; }
~ExampleVirt() { print_destroyed(this); }
~ExampleVirt() {
cout << "Destructing ExampleVirt.." << endl;
}
virtual int run(int value) { virtual int run(int value) {
std::cout << "Original implementation of ExampleVirt::run(state=" << state std::cout << "Original implementation of ExampleVirt::run(state=" << state
@ -71,8 +69,8 @@ public:
class NonCopyable { class NonCopyable {
public: public:
NonCopyable(int a, int b) : value{new int(a*b)} {} NonCopyable(int a, int b) : value{new int(a*b)} { print_created(this, a, b); }
NonCopyable(NonCopyable &&) = default; NonCopyable(NonCopyable &&o) { value = std::move(o.value); print_move_created(this); }
NonCopyable(const NonCopyable &) = delete; NonCopyable(const NonCopyable &) = delete;
NonCopyable() = delete; NonCopyable() = delete;
void operator=(const NonCopyable &) = delete; void operator=(const NonCopyable &) = delete;
@ -80,7 +78,7 @@ public:
std::string get_value() const { std::string get_value() const {
if (value) return std::to_string(*value); else return "(null)"; if (value) return std::to_string(*value); else return "(null)";
} }
~NonCopyable() { std::cout << "NonCopyable destructor @ " << this << "; value = " << get_value() << std::endl; } ~NonCopyable() { print_destroyed(this); }
private: private:
std::unique_ptr<int> value; std::unique_ptr<int> value;
@ -90,11 +88,11 @@ private:
// when it is not referenced elsewhere, but copied if it is still referenced. // when it is not referenced elsewhere, but copied if it is still referenced.
class Movable { class Movable {
public: public:
Movable(int a, int b) : value{a+b} {} Movable(int a, int b) : value{a+b} { print_created(this, a, b); }
Movable(const Movable &m) { value = m.value; std::cout << "Movable @ " << this << " copy constructor" << std::endl; } Movable(const Movable &m) { value = m.value; print_copy_created(this); }
Movable(Movable &&m) { value = std::move(m.value); std::cout << "Movable @ " << this << " move constructor" << std::endl; } Movable(Movable &&m) { value = std::move(m.value); print_move_created(this); }
int get_value() const { return value; } int get_value() const { return value; }
~Movable() { std::cout << "Movable destructor @ " << this << "; value = " << get_value() << std::endl; } ~Movable() { print_destroyed(this); }
private: private:
int value; int value;
}; };
@ -305,5 +303,6 @@ void init_ex_virtual_functions(py::module &m) {
m.def("runExampleVirtBool", &runExampleVirtBool); m.def("runExampleVirtBool", &runExampleVirtBool);
m.def("runExampleVirtVirtual", &runExampleVirtVirtual); m.def("runExampleVirtVirtual", &runExampleVirtVirtual);
m.def("cstats_debug", &ConstructorStats::get<ExampleVirt>);
initialize_inherited_virtuals(m); initialize_inherited_virtuals(m);
} }

View File

@ -37,8 +37,6 @@ print(runExampleVirt(ex12p, 20))
print(runExampleVirtBool(ex12p)) print(runExampleVirtBool(ex12p))
runExampleVirtVirtual(ex12p) runExampleVirtVirtual(ex12p)
sys.stdout.flush()
class VI_AR(A_Repeat): class VI_AR(A_Repeat):
def unlucky_number(self): def unlucky_number(self):
return 99 return 99
@ -122,3 +120,16 @@ try:
except RuntimeError as e: except RuntimeError as e:
# Don't print the exception message here because it differs under debug/non-debug mode # Don't print the exception message here because it differs under debug/non-debug mode
print("Caught expected exception") print("Caught expected exception")
from example import ConstructorStats
del ex12
del ex12p
del obj
del ncv1
del ncv2
cstats = [ConstructorStats.get(ExampleVirt), ConstructorStats.get(NonCopyable), ConstructorStats.get(Movable)]
print("Instances not destroyed:", [x.alive() for x in cstats])
print("Constructor values:", [x.values() for x in cstats])
print("Copy constructions:", [x.copy_constructions for x in cstats])
print("Move constructions:", [cstats[i].move_constructions >= 1 for i in range(1, len(cstats))])

View File

@ -1,8 +1,8 @@
Constructing ExampleVirt.. ### ExampleVirt @ 0x2073a90 created 10
Original implementation of ExampleVirt::run(state=10, value=20) Original implementation of ExampleVirt::run(state=10, value=20)
30 30
Caught expected exception: Tried to call pure virtual function "ExampleVirt::pure_virtual" Caught expected exception: Tried to call pure virtual function "ExampleVirt::pure_virtual"
Constructing ExampleVirt.. ### ExampleVirt @ 0x2076a00 created 11
ExtendedExampleVirt::run(20), calling parent.. ExtendedExampleVirt::run(20), calling parent..
Original implementation of ExampleVirt::run(state=11, value=21) Original implementation of ExampleVirt::run(state=11, value=21)
32 32
@ -78,20 +78,29 @@ VI_DT says: quack quack quack
Unlucky = 1234 Unlucky = 1234
Lucky = -4.25 Lucky = -4.25
2^2 * 3^2 = 2^2 * 3^2 =
NonCopyable destructor @ 0x1a6c3f0; value = (null) ### NonCopyable @ 0x207df10 created 4 9
### NonCopyable @ 0x7ffcfe866228 created via move constructor
### NonCopyable @ 0x207df10 destroyed
36 36
NonCopyable destructor @ 0x7ffc6d1fbaa8; value = 36 ### NonCopyable @ 0x7ffcfe866228 destroyed
4 + 5 = 4 + 5 =
Movable @ 0x7ffc6d1fbacc copy constructor ### Movable @ 0x207e230 created 4 5
### Movable @ 0x7ffcfe86624c created via copy constructor
9 9
Movable destructor @ 0x7ffc6d1fbacc; value = 9 ### Movable @ 0x7ffcfe86624c destroyed
7 + 7 = 7 + 7 =
Movable @ 0x7ffc6d1fbacc move constructor ### Movable @ 0x20259e0 created 7 7
Movable destructor @ 0x1a6c4d0; value = 14 ### Movable @ 0x7ffcfe86624c created via move constructor
### Movable @ 0x20259e0 destroyed
14 14
Movable destructor @ 0x7ffc6d1fbacc; value = 14 ### Movable @ 0x7ffcfe86624c destroyed
### NonCopyable @ 0x2025a00 created 9 9
Caught expected exception Caught expected exception
NonCopyable destructor @ 0x29a64b0; value = 81 ### ExampleVirt @ 0x2073a90 destroyed
Movable destructor @ 0x1a6c410; value = 9 ### ExampleVirt @ 0x2076a00 destroyed
Destructing ExampleVirt.. ### Movable @ 0x207e230 destroyed
Destructing ExampleVirt.. ### NonCopyable @ 0x2025a00 destroyed
Instances not destroyed: [0, 0, 0]
Constructor values: [['10', '11'], ['4', '9', '9', '9'], ['4', '5', '7', '7']]
Copy constructions: [0, 0, 1]
Move constructions: [True, True]

View File

@ -8,6 +8,7 @@
*/ */
#include "example.h" #include "example.h"
#include "constructor-stats.h"
void init_ex_methods_and_attributes(py::module &); void init_ex_methods_and_attributes(py::module &);
void init_ex_python_types(py::module &); void init_ex_python_types(py::module &);
@ -34,9 +35,24 @@ void init_issues(py::module &);
void init_eigen(py::module &); void init_eigen(py::module &);
#endif #endif
void bind_ConstructorStats(py::module &m) {
py::class_<ConstructorStats>(m, "ConstructorStats")
.def("alive", &ConstructorStats::alive)
.def("values", &ConstructorStats::values)
.def_readwrite("default_constructions", &ConstructorStats::default_constructions)
.def_readwrite("copy_assignments", &ConstructorStats::copy_assignments)
.def_readwrite("move_assignments", &ConstructorStats::move_assignments)
.def_readwrite("copy_constructions", &ConstructorStats::copy_constructions)
.def_readwrite("move_constructions", &ConstructorStats::move_constructions)
.def_static("get", (ConstructorStats &(*)(py::object)) &ConstructorStats::get, py::return_value_policy::reference_internal)
;
}
PYBIND11_PLUGIN(example) { PYBIND11_PLUGIN(example) {
py::module m("example", "pybind example plugin"); py::module m("example", "pybind example plugin");
bind_ConstructorStats(m);
init_ex_methods_and_attributes(m); init_ex_methods_and_attributes(m);
init_ex_python_types(m); init_ex_python_types(m);
init_ex_operator_overloading(m); init_ex_operator_overloading(m);

View File

@ -8,11 +8,18 @@
*/ */
#include "example.h" #include "example.h"
#include "constructor-stats.h"
#include <pybind11/stl.h> #include <pybind11/stl.h>
#include <pybind11/operators.h> #include <pybind11/operators.h>
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>); PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>);
#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) { void init_issues(py::module &m) {
py::module m2 = m.def_submodule("issues"); py::module m2 = m.def_submodule("issues");
@ -159,12 +166,6 @@ void init_issues(py::module &m) {
; ;
// Issue #328: first member in a class can't be used in operators // Issue #328: first member in a class can't be used in operators
#define TRACKERS(CLASS) CLASS() { std::cout << #CLASS "@" << this << " constructor\n"; } \
~CLASS() { std::cout << #CLASS "@" << this << " destructor\n"; }
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) };
py::class_<NestABase>(m2, "NestABase").def(py::init<>()).def_readwrite("value", &NestABase::value); 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()) 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); .def("as_base", [](NestA &a) -> NestABase& { return (NestABase&) a; }, py::return_value_policy::reference_internal);

View File

@ -24,15 +24,15 @@ Failed as expected: Incompatible constructor arguments. The following argument t
1. example.issues.StrIssue(arg0: int) 1. example.issues.StrIssue(arg0: int)
2. example.issues.StrIssue() 2. example.issues.StrIssue()
Invoked with: no, such, constructor Invoked with: no, such, constructor
NestABase@0x1152940 constructor ### NestABase @ 0x15eb630 created via default constructor
NestA@0x1152940 constructor ### NestA @ 0x15eb630 created via default constructor
NestABase@0x11f9350 constructor ### NestABase @ 0x1704000 created via default constructor
NestA@0x11f9350 constructor ### NestA @ 0x1704000 created via default constructor
NestB@0x11f9350 constructor ### NestB @ 0x1704000 created via default constructor
NestABase@0x112d0d0 constructor ### NestABase @ 0x1633110 created via default constructor
NestA@0x112d0d0 constructor ### NestA @ 0x1633110 created via default constructor
NestB@0x112d0d0 constructor ### NestB @ 0x1633110 created via default constructor
NestC@0x112d0d0 constructor ### NestC @ 0x1633110 created via default constructor
13 13
103 103
1003 1003
@ -43,13 +43,13 @@ NestC@0x112d0d0 constructor
42 42
-2 -2
42 42
NestC@0x112d0d0 destructor ### NestC @ 0x1633110 destroyed
NestB@0x112d0d0 destructor ### NestB @ 0x1633110 destroyed
NestA@0x112d0d0 destructor ### NestA @ 0x1633110 destroyed
NestABase@0x112d0d0 destructor ### NestABase @ 0x1633110 destroyed
42 42
NestA@0x1152940 destructor ### NestA @ 0x15eb630 destroyed
NestABase@0x1152940 destructor ### NestABase @ 0x15eb630 destroyed
NestB@0x11f9350 destructor ### NestB @ 0x1704000 destroyed
NestA@0x11f9350 destructor ### NestA @ 0x1704000 destroyed
NestABase@0x11f9350 destructor ### NestABase @ 0x1704000 destroyed

View File

@ -2,15 +2,16 @@
#define __OBJECT_H #define __OBJECT_H
#include <atomic> #include <atomic>
#include "constructor-stats.h"
/// Reference counted object base class /// Reference counted object base class
class Object { class Object {
public: public:
/// Default constructor /// Default constructor
Object() { } Object() { print_default_created(this); }
/// Copy constructor /// Copy constructor
Object(const Object &) : m_refCount(0) {} Object(const Object &) : m_refCount(0) { print_copy_created(this); }
/// Return the current reference count /// Return the current reference count
int getRefCount() const { return m_refCount; }; int getRefCount() const { return m_refCount; };
@ -37,11 +38,17 @@ protected:
/** \brief Virtual protected deconstructor. /** \brief Virtual protected deconstructor.
* (Will only be called by \ref ref) * (Will only be called by \ref ref)
*/ */
virtual ~Object() { } virtual ~Object() { print_destroyed(this); }
private: private:
mutable std::atomic<int> m_refCount { 0 }; mutable std::atomic<int> m_refCount { 0 };
}; };
// Tag class used to track constructions of ref objects. When we track constructors, below, we
// track and print out the actual class (e.g. ref<MyObject>), and *also* add a fake tracker for
// ref_tag. This lets us check that the total number of ref<Anything> constructors/destructors is
// correct without having to check each individual ref<Whatever> type individually.
class ref_tag {};
/** /**
* \brief Reference counting helper * \brief Reference counting helper
* *
@ -55,37 +62,43 @@ private:
template <typename T> class ref { template <typename T> class ref {
public: public:
/// Create a nullptr reference /// Create a nullptr reference
ref() : m_ptr(nullptr) { std::cout << "Created empty ref" << std::endl; } ref() : m_ptr(nullptr) { print_default_created(this); track_default_created((ref_tag*) this); }
/// Construct a reference from a pointer /// Construct a reference from a pointer
ref(T *ptr) : m_ptr(ptr) { ref(T *ptr) : m_ptr(ptr) {
std::cout << "Initialized ref from pointer " << ptr<< std::endl;
if (m_ptr) ((Object *) m_ptr)->incRef(); if (m_ptr) ((Object *) m_ptr)->incRef();
print_created(this, "from pointer", m_ptr); track_created((ref_tag*) this, "from pointer");
} }
/// Copy constructor /// Copy constructor
ref(const ref &r) : m_ptr(r.m_ptr) { ref(const ref &r) : m_ptr(r.m_ptr) {
std::cout << "Initialized ref from ref " << r.m_ptr << std::endl;
if (m_ptr) if (m_ptr)
((Object *) m_ptr)->incRef(); ((Object *) m_ptr)->incRef();
print_copy_created(this, "with pointer", m_ptr); track_copy_created((ref_tag*) this);
} }
/// Move constructor /// Move constructor
ref(ref &&r) : m_ptr(r.m_ptr) { ref(ref &&r) : m_ptr(r.m_ptr) {
std::cout << "Initialized ref with move from ref " << r.m_ptr << std::endl;
r.m_ptr = nullptr; r.m_ptr = nullptr;
print_move_created(this, "with pointer", m_ptr); track_move_created((ref_tag*) this);
} }
/// Destroy this reference /// Destroy this reference
~ref() { ~ref() {
std::cout << "Destructing ref " << m_ptr << std::endl;
if (m_ptr) if (m_ptr)
((Object *) m_ptr)->decRef(); ((Object *) m_ptr)->decRef();
print_destroyed(this); track_destroyed((ref_tag*) this);
} }
/// Move another reference into the current one /// Move another reference into the current one
ref& operator=(ref&& r) { ref& operator=(ref&& r) {
std::cout << "Move-assigning ref " << r.m_ptr << std::endl; print_move_assigned(this, "pointer", r.m_ptr); track_move_assigned((ref_tag*) this);
if (*this == r) if (*this == r)
return *this; return *this;
if (m_ptr) if (m_ptr)
@ -97,7 +110,8 @@ public:
/// Overwrite this reference with another reference /// Overwrite this reference with another reference
ref& operator=(const ref& r) { ref& operator=(const ref& r) {
std::cout << "Assigning ref " << r.m_ptr << std::endl; print_copy_assigned(this, "pointer", r.m_ptr); track_copy_assigned((ref_tag*) this);
if (m_ptr == r.m_ptr) if (m_ptr == r.m_ptr)
return *this; return *this;
if (m_ptr) if (m_ptr)
@ -110,7 +124,8 @@ public:
/// Overwrite this reference with a pointer to another object /// Overwrite this reference with a pointer to another object
ref& operator=(T *ptr) { ref& operator=(T *ptr) {
std::cout << "Assigning ptr " << ptr << " to ref" << std::endl; print_values(this, "assigned pointer"); track_values((ref_tag*) this, "assigned pointer");
if (m_ptr == ptr) if (m_ptr == ptr)
return *this; return *this;
if (m_ptr) if (m_ptr)

View File

@ -9,14 +9,16 @@ remove_long_marker = re.compile(r'([0-9])L')
remove_hex = re.compile(r'0x[0-9a-fA-F]+') remove_hex = re.compile(r'0x[0-9a-fA-F]+')
shorten_floats = re.compile(r'([1-9][0-9]*\.[0-9]{4})[0-9]*') shorten_floats = re.compile(r'([1-9][0-9]*\.[0-9]{4})[0-9]*')
relaxed = False
def sanitize(lines): def sanitize(lines):
lines = lines.split('\n') lines = lines.split('\n')
for i in range(len(lines)): for i in range(len(lines)):
line = lines[i] line = lines[i]
if line.startswith(" |"): if line.startswith(" |"):
line = "" line = ""
if line.startswith("### "):
# Constructor/destructor output. Useful for example, but unreliable across compilers;
# testing of proper construction/destruction occurs with ConstructorStats mechanism instead
line = ""
line = remove_unicode_marker.sub(r'\1', line) line = remove_unicode_marker.sub(r'\1', line)
line = remove_long_marker.sub(r'\1', line) line = remove_long_marker.sub(r'\1', line)
line = remove_hex.sub(r'0', line) line = remove_hex.sub(r'0', line)
@ -28,13 +30,6 @@ def sanitize(lines):
line = line.replace('example.EMode', 'EMode') line = line.replace('example.EMode', 'EMode')
line = line.replace('method of builtins.PyCapsule instance', '') line = line.replace('method of builtins.PyCapsule instance', '')
line = line.strip() line = line.strip()
if relaxed:
lower = line.lower()
# The precise pattern of allocations and deallocations is dependent on the compiler
# and optimization level, so we unfortunately can't reliably check it in this kind of test case
if 'constructor' in lower or 'destructor' in lower \
or 'ref' in lower or 'freeing' in lower:
line = ""
lines[i] = line lines[i] = line
return '\n'.join(sorted([l for l in lines if l != ""])) return '\n'.join(sorted([l for l in lines if l != ""]))
@ -44,16 +39,12 @@ if path != '':
os.chdir(path) os.chdir(path)
if len(sys.argv) < 2: if len(sys.argv) < 2:
print("Syntax: %s [--relaxed] <test name>" % sys.argv[0]) print("Syntax: %s <test name>" % sys.argv[0])
exit(0) exit(0)
if len(sys.argv) == 3 and sys.argv[1] == '--relaxed':
del sys.argv[1]
relaxed = True
name = sys.argv[1] name = sys.argv[1]
try: try:
output_bytes = subprocess.check_output([sys.executable, name + ".py"], output_bytes = subprocess.check_output([sys.executable, "-u", name + ".py"],
stderr=subprocess.STDOUT) stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
if e.returncode == 99: if e.returncode == 99: