diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 93e4d4525..22d44c808 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -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}") diff --git a/example/constructor-stats.h b/example/constructor-stats.h new file mode 100644 index 000000000..0f505c140 --- /dev/null +++ b/example/constructor-stats.h @@ -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 + + 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, 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 +#include +#include +#include + +class ConstructorStats { +protected: + std::unordered_map _instances; // Need a map rather than set because members can shared address with parents + std::list _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 void value(const T &v, Tmore &&...args) { + std::ostringstream oss; + oss << v; + _values.push_back(oss.str()); + value(std::forward(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 all_cstats; + return all_cstats[type]; + } + + // Gets constructor stats from a C++ type + template 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 void track_copy_created(T *inst) { ConstructorStats::get().copy_created(inst); } +template void track_move_created(T *inst) { ConstructorStats::get().move_created(inst); } +template void track_copy_assigned(T *, Values &&...values) { + auto &cst = ConstructorStats::get(); + cst.copy_assignments++; + cst.value(std::forward(values)...); +} +template void track_move_assigned(T *, Values &&...values) { + auto &cst = ConstructorStats::get(); + cst.move_assignments++; + cst.value(std::forward(values)...); +} +template void track_default_created(T *inst, Values &&...values) { + auto &cst = ConstructorStats::get(); + cst.default_created(inst); + cst.value(std::forward(values)...); +} +template void track_created(T *inst, Values &&...values) { + auto &cst = ConstructorStats::get(); + cst.created(inst); + cst.value(std::forward(values)...); +} +template void track_destroyed(T *inst) { + ConstructorStats::get().destroyed(inst); +} +template void track_values(T *, Values &&...values) { + ConstructorStats::get().value(std::forward(values)...); +} + +inline void print_constr_details_more() { std::cout << std::endl; } +template void print_constr_details_more(const Head &head, Tail &&...tail) { + std::cout << " " << head; + print_constr_details_more(std::forward(tail)...); +} +template void print_constr_details(T *inst, const std::string &action, Output &&...output) { + std::cout << "### " << py::type_id() << " @ " << inst << " " << action; + print_constr_details_more(std::forward(output)...); +} + +// Verbose versions of the above: +template 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 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 void print_copy_assigned(T *inst, Values &&...values) { + print_constr_details(inst, "assigned via copy assignment", values...); + track_copy_assigned(inst, values...); +} +template void print_move_assigned(T *inst, Values &&...values) { + print_constr_details(inst, "assigned via move assignment", values...); + track_move_assigned(inst, values...); +} +template void print_default_created(T *inst, Values &&...values) { + print_constr_details(inst, "created via default constructor", values...); + track_default_created(inst, values...); +} +template void print_created(T *inst, Values &&...values) { + print_constr_details(inst, "created", values...); + track_created(inst, values...); +} +template void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values + print_constr_details(inst, "destroyed", values...); + track_destroyed(inst); +} +template void print_values(T *inst, Values &&...values) { + print_constr_details(inst, ":", values...); + track_values(inst, values...); +} + diff --git a/example/example-buffers.cpp b/example/example-buffers.cpp index 709770b59..17c8d271f 100644 --- a/example/example-buffers.cpp +++ b/example/example-buffers.cpp @@ -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) } ); - }); + }) + ; } diff --git a/example/example-buffers.py b/example/example-buffers.py index 48eda27bd..185e6d3cc 100755 --- a/example/example-buffers.py +++ b/example/example-buffers.py @@ -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) diff --git a/example/example-buffers.ref b/example/example-buffers.ref index 75db9633b..1e02ffd97 100644 --- a/example/example-buffers.ref +++ b/example/example-buffers.ref @@ -1,4 +1,4 @@ -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 diff --git a/example/example-callbacks.cpp b/example/example-callbacks.cpp index a7a583e67..afb864db2 100644 --- a/example/example-callbacks.cpp +++ b/example/example-callbacks.cpp @@ -8,6 +8,7 @@ */ #include "example.h" +#include "constructor-stats.h" #include @@ -57,6 +58,21 @@ void test_dummy_function(const std::function &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 { 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); } diff --git a/example/example-callbacks.py b/example/example-callbacks.py index 1d18c892e..89f83a1b8 100755 --- a/example/example-callbacks.py +++ b/example/example-callbacks.py @@ -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 diff --git a/example/example-callbacks.ref b/example/example-callbacks.ref index 7180b585b..72f751a0f 100644 --- a/example/example-callbacks.ref +++ b/example/example-callbacks.ref @@ -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: + Invoked with: 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] + diff --git a/example/example-methods-and-attributes.cpp b/example/example-methods-and-attributes.cpp index 800078cf5..edf519aa9 100644 --- a/example/example-methods-and-attributes.cpp +++ b/example/example-methods-and-attributes.cpp @@ -8,32 +8,25 @@ BSD-style license that can be found in the LICENSE file. */ +#include +#include #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) + ; } diff --git a/example/example-methods-and-attributes.py b/example/example-methods-and-attributes.py index 229e1c987..8f2a5243d 100755 --- a/example/example-methods-and-attributes.py +++ b/example/example-methods-and-attributes.py @@ -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) diff --git a/example/example-methods-and-attributes.ref b/example/example-methods-and-attributes.ref index 95277903d..338539e76 100644 --- a/example/example-methods-and-attributes.ref +++ b/example/example-methods-and-attributes.ref @@ -1,16 +1,16 @@ -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 diff --git a/example/example-modules.cpp b/example/example-modules.cpp index fdaf72ece..bd25af17f 100644 --- a/example/example-modules.cpp +++ b/example/example-modules.cpp @@ -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 ©) { 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 ©) { print_copy_assigned(this); a1 = copy.a1; a2 = copy.a2; return *this; } A &get_a1() { return a1; } A &get_a2() { return a2; } diff --git a/example/example-modules.py b/example/example-modules.py index 2262fcf5c..91c075c75 100755 --- a/example/example-modules.py +++ b/example/example-modules.py @@ -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]) diff --git a/example/example-modules.ref b/example/example-modules.ref index 9c783135c..1ca078349 100644 --- a/example/example-modules.ref +++ b/example/example-modules.ref @@ -1,22 +1,31 @@ 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] diff --git a/example/example-opaque-types.py b/example/example-opaque-types.py index 8fd09f6cd..103563469 100644 --- a/example/example-opaque-types.py +++ b/example/example-opaque-types.py @@ -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 diff --git a/example/example-opaque-types.ref b/example/example-opaque-types.ref index f8f169204..12b87d35b 100644 --- a/example/example-opaque-types.ref +++ b/example/example-opaque-types.ref @@ -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 - + Opaque list: [some value] diff --git a/example/example-operator-overloading.cpp b/example/example-operator-overloading.cpp index ff5e658a9..f6b9967bc 100644 --- a/example/example-operator-overloading.cpp +++ b/example/example-operator-overloading.cpp @@ -8,27 +8,28 @@ */ #include "example.h" +#include "constructor-stats.h" #include 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_(m, "Vector2") .def(py::init()) @@ -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"); } diff --git a/example/example-operator-overloading.py b/example/example-operator-overloading.py index 591ae391c..4253a75af 100755 --- a/example/example-operator-overloading.py +++ b/example/example-operator-overloading.py @@ -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) diff --git a/example/example-operator-overloading.ref b/example/example-operator-overloading.ref index 8f2c29886..82365312a 100644 --- a/example/example-operator-overloading.ref +++ b/example/example-operator-overloading.ref @@ -1,57 +1,66 @@ -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 diff --git a/example/example-python-types.cpp b/example/example-python-types.cpp index 976583577..5decede9a 100644 --- a/example/example-python-types.cpp +++ b/example/example-python-types.cpp @@ -9,6 +9,7 @@ */ #include "example.h" +#include "constructor-stats.h" #include #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)") + ; } diff --git a/example/example-python-types.py b/example/example-python-types.py index df5a244b9..b77a2c531 100755 --- a/example/example-python-types.py +++ b/example/example-python-types.py @@ -65,3 +65,10 @@ print("__name__(example.ExamplePythonTypes) = %s" % ExamplePythonTypes.__name__) print("__module__(example.ExamplePythonTypes) = %s" % ExamplePythonTypes.__module__) print("__name__(example.ExamplePythonTypes.get_set) = %s" % ExamplePythonTypes.get_set.__name__) 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()) diff --git a/example/example-python-types.ref b/example/example-python-types.ref index a8c39e081..052cce0ea 100644 --- a/example/example-python-types.ref +++ b/example/example-python-types.ref @@ -2,6 +2,7 @@ 5 example.ExamplePythonTypes: No constructor defined! can't set attribute +### ExamplePythonTypes @ 0x1045b80 created via new_instance key: key2, value=value2 key: key, value=value key: key, value=value @@ -134,4 +135,6 @@ __name__(example.ExamplePythonTypes) = ExamplePythonTypes __module__(example.ExamplePythonTypes) = example __name__(example.ExamplePythonTypes.get_set) = get_set __module__(example.ExamplePythonTypes.get_set) = example -Destructing ExamplePythonTypes +Instances not destroyed: 1 +### ExamplePythonTypes @ 0x1045b80 destroyed +Instances not destroyed: 0 diff --git a/example/example-sequences-and-iterators.cpp b/example/example-sequences-and-iterators.cpp index 423364984..c677f15d7 100644 --- a/example/example-sequences-and-iterators.cpp +++ b/example/example-sequences-and-iterators.cpp @@ -9,51 +9,55 @@ */ #include "example.h" +#include "constructor-stats.h" #include #include class Sequence { public: 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]; memset(m_data, 0, sizeof(float) * size); } Sequence(const std::vector &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]; memcpy(m_data, &value[0], sizeof(float) * 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]; memcpy(m_data, s.m_data, sizeof(float)*m_size); } 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_data = nullptr; } ~Sequence() { - std::cout << "Freeing a sequence with " << m_size << " entries" << std::endl; + print_destroyed(this); delete[] m_data; } Sequence &operator=(const Sequence &s) { - std::cout << "Assignment operator: Creating a sequence with " << s.m_size << " entries" << std::endl; - delete[] m_data; - m_size = s.m_size; - m_data = new float[m_size]; - memcpy(m_data, s.m_data, sizeof(float)*m_size); + if (&s != this) { + delete[] m_data; + m_size = s.m_size; + m_data = new float[m_size]; + memcpy(m_data, s.m_data, sizeof(float)*m_size); + } + + print_copy_assigned(this); + return *this; } Sequence &operator=(Sequence &&s) { - std::cout << "Move assignment operator: Creating a sequence with " << s.m_size << " entries" << std::endl; if (&s != this) { delete[] m_data; m_size = s.m_size; @@ -61,6 +65,9 @@ public: s.m_size = 0; s.m_data = nullptr; } + + print_move_assigned(this); + return *this; } diff --git a/example/example-sequences-and-iterators.py b/example/example-sequences-and-iterators.py index 5a014dda9..69ec84ed9 100755 --- a/example/example-sequences-and-iterators.py +++ b/example/example-sequences-and-iterators.py @@ -28,3 +28,19 @@ rev[0::2] = Sequence([2.0, 2.0, 2.0]) for i in rev: print(i, end=' ') 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) diff --git a/example/example-sequences-and-iterators.ref b/example/example-sequences-and-iterators.ref index c02dc76af..909a93abe 100644 --- a/example/example-sequences-and-iterators.ref +++ b/example/example-sequences-and-iterators.ref @@ -1,21 +1,31 @@ -Value constructor: Creating a sequence with 5 entries -s = +### Sequence @ 0x1535b00 created of size 5 +s = len(s) = 5 s[0], s[3] = 0.000000 0.000000 12.34 in s: False 12.34 in s: True s[0], s[3] = 12.340000 56.779999 -Value constructor: Creating a sequence with 5 entries -Move constructor: Creating a sequence with 5 entries -Freeing a sequence with 0 entries -Value constructor: Creating a sequence with 5 entries +### Sequence @ 0x7fff22a45068 created of size 5 +### Sequence @ 0x1538b90 created via move constructor +### Sequence @ 0x7fff22a45068 destroyed +### 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 -0.0 56.7799987793 0.0 0.0 12.3400001526 -0.0 56.7799987793 0.0 0.0 12.3400001526 +0.0 56.779998779296875 0.0 0.0 12.34000015258789 +0.0 56.779998779296875 0.0 0.0 12.34000015258789 True -Value constructor: Creating a sequence with 3 entries -Freeing a sequence with 3 entries -2.0 56.7799987793 2.0 0.0 2.0 -Freeing a sequence with 5 entries -Freeing a sequence with 5 entries -Freeing a sequence with 5 entries +### Sequence @ 0x153c4b0 created of size 3 from std::vector +### Sequence @ 0x153c4b0 destroyed +2.0 56.779998779296875 2.0 0.0 2.0 +Instances not destroyed: 3 +### Sequence @ 0x1535b00 destroyed +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 diff --git a/example/example-smart-ptr.cpp b/example/example-smart-ptr.cpp index 7ae1db8d5..c4dd2c8d8 100644 --- a/example/example-smart-ptr.cpp +++ b/example/example-smart-ptr.cpp @@ -15,7 +15,7 @@ class MyObject1 : public Object { public: MyObject1(int value) : value(value) { - std::cout << toString() << " constructor" << std::endl; + print_created(this, toString()); } std::string toString() const { @@ -24,7 +24,7 @@ public: protected: virtual ~MyObject1() { - std::cout << toString() << " destructor" << std::endl; + print_destroyed(this); } private: @@ -35,7 +35,7 @@ private: class MyObject2 { public: MyObject2(int value) : value(value) { - std::cout << toString() << " constructor" << std::endl; + print_created(this, toString()); } std::string toString() const { @@ -43,7 +43,7 @@ public: } virtual ~MyObject2() { - std::cout << toString() << " destructor" << std::endl; + print_destroyed(this); } private: @@ -54,7 +54,7 @@ private: class MyObject3 : public std::enable_shared_from_this { public: MyObject3(int value) : value(value) { - std::cout << toString() << " constructor" << std::endl; + print_created(this, toString()); } std::string toString() const { @@ -62,7 +62,7 @@ public: } virtual ~MyObject3() { - std::cout << toString() << " destructor" << std::endl; + print_destroyed(this); } private: @@ -144,4 +144,7 @@ void init_ex_smart_ptr(py::module &m) { m.def("print_myobject3_4", &print_myobject3_4); py::implicitly_convertible(); + + // Expose constructor stats for the ref type + m.def("cstats_ref", &ConstructorStats::get); } diff --git a/example/example-smart-ptr.py b/example/example-smart-ptr.py index b12c5b999..fe23e1131 100755 --- a/example/example-smart-ptr.py +++ b/example/example-smart-ptr.py @@ -68,3 +68,18 @@ for o in [MyObject3(9), make_myobject3_1(), make_myobject3_2()]: print_myobject3_2(o) print_myobject3_3(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]) diff --git a/example/example-smart-ptr.ref b/example/example-smart-ptr.ref index 641abd0c4..52ffa8bf1 100644 --- a/example/example-smart-ptr.ref +++ b/example/example-smart-ptr.ref @@ -1,243 +1,270 @@ -MyObject1[1] constructor -Initialized ref from pointer 0x1347ba0 -MyObject1[2] constructor -Initialized ref from pointer 0x12b9270 -Initialized ref from ref 0x12b9270 -Destructing ref 0x12b9270 -MyObject1[3] constructor -Initialized ref from pointer 0x12a2a90 -MyObject1[1] -Created empty ref -Assigning ref 0x1347ba0 -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 +### Object @ 0xdeffd0 created via default constructor +### MyObject1 @ 0xdeffd0 created MyObject1[1] +### ref @ 0x7f6a2e03c4a8 created from pointer 0xdeffd0 +### Object @ 0xe43f50 created via default constructor +### MyObject1 @ 0xe43f50 created MyObject1[2] +### ref @ 0x7fff136845d0 created from pointer 0xe43f50 +### ref @ 0x7f6a2c32aad8 created via copy constructor with pointer 0xe43f50 +### ref @ 0x7fff136845d0 destroyed +### Object @ 0xee8cf0 created via default constructor +### MyObject1 @ 0xee8cf0 created MyObject1[3] +### ref @ 0x7f6a2c32ab08 created from pointer 0xee8cf0 Reference count = 1 +MyObject1[1] +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xdeffd0 +### ref @ 0x7fff136845a8 created via copy constructor with pointer 0xdeffd0 +MyObject1[1] +### ref @ 0x7fff136845a8 destroyed +### ref @ 0x7fff136845c8 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xdeffd0 +MyObject1[1] +### ref @ 0x7fff136845c8 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xdeffd0 +MyObject1[1] +### ref @ 0x7fff136845c8 destroyed Reference count = 1 +MyObject1[2] +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xe43f50 +### ref @ 0x7fff136845a8 created via copy constructor with pointer 0xe43f50 +MyObject1[2] +### ref @ 0x7fff136845a8 destroyed +### ref @ 0x7fff136845c8 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xe43f50 +MyObject1[2] +### ref @ 0x7fff136845c8 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xe43f50 +MyObject1[2] +### ref @ 0x7fff136845c8 destroyed Reference count = 1 - - - +MyObject1[3] +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8cf0 +### ref @ 0x7fff136845a8 created via copy constructor with pointer 0xee8cf0 +MyObject1[3] +### ref @ 0x7fff136845a8 destroyed +### ref @ 0x7fff136845c8 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8cf0 +MyObject1[3] +### ref @ 0x7fff136845c8 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8cf0 +MyObject1[3] +### ref @ 0x7fff136845c8 destroyed +### MyObject1 @ 0xe43f50 destroyed +### Object @ 0xe43f50 destroyed +### ref @ 0x7f6a2c32aad8 destroyed +### MyObject1 @ 0xdeffd0 destroyed +### Object @ 0xdeffd0 destroyed +### ref @ 0x7f6a2e03c4a8 destroyed +### Object @ 0xee8310 created via default constructor +### MyObject1 @ 0xee8310 created MyObject1[4] +### ref @ 0x7f6a2e03c4a8 created from pointer 0xee8310 +### Object @ 0xee8470 created via default constructor +### MyObject1 @ 0xee8470 created MyObject1[5] +### ref @ 0x7fff136845d0 created from pointer 0xee8470 +### ref @ 0x7f6a2c32aad8 created via copy constructor with pointer 0xee8470 +### ref @ 0x7fff136845d0 destroyed +### Object @ 0xee95a0 created via default constructor +### MyObject1 @ 0xee95a0 created MyObject1[6] +### ref @ 0x7f6a2c32ab38 created from pointer 0xee95a0 +### MyObject1 @ 0xee8cf0 destroyed +### Object @ 0xee8cf0 destroyed +### ref @ 0x7f6a2c32ab08 destroyed + +MyObject1[4] +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8310 +### ref @ 0x7fff136845a8 created via copy constructor with pointer 0xee8310 +MyObject1[4] +### ref @ 0x7fff136845a8 destroyed +### ref @ 0x7fff136845c8 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8310 +MyObject1[4] +### ref @ 0x7fff136845c8 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8310 +MyObject1[4] +### ref @ 0x7fff136845c8 destroyed +MyObject1[4] +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8310 +### ref @ 0x7fff136845a8 created via copy constructor with pointer 0xee8310 +MyObject1[4] +### ref @ 0x7fff136845a8 destroyed +### ref @ 0x7fff136845c8 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8310 +MyObject1[4] +### ref @ 0x7fff136845c8 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8310 +MyObject1[4] +### ref @ 0x7fff136845c8 destroyed + +MyObject1[5] +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8470 +### ref @ 0x7fff136845a8 created via copy constructor with pointer 0xee8470 +MyObject1[5] +### ref @ 0x7fff136845a8 destroyed +### ref @ 0x7fff136845c8 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8470 +MyObject1[5] +### ref @ 0x7fff136845c8 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8470 +MyObject1[5] +### ref @ 0x7fff136845c8 destroyed +MyObject1[5] +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8470 +### ref @ 0x7fff136845a8 created via copy constructor with pointer 0xee8470 +MyObject1[5] +### ref @ 0x7fff136845a8 destroyed +### ref @ 0x7fff136845c8 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8470 +MyObject1[5] +### ref @ 0x7fff136845c8 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee8470 +MyObject1[5] +### ref @ 0x7fff136845c8 destroyed + +MyObject1[6] +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee95a0 +### ref @ 0x7fff136845a8 created via copy constructor with pointer 0xee95a0 +MyObject1[6] +### ref @ 0x7fff136845a8 destroyed +### ref @ 0x7fff136845c8 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee95a0 +MyObject1[6] +### ref @ 0x7fff136845c8 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee95a0 +MyObject1[6] +### ref @ 0x7fff136845c8 destroyed +MyObject1[6] +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee95a0 +### ref @ 0x7fff136845a8 created via copy constructor with pointer 0xee95a0 +MyObject1[6] +### ref @ 0x7fff136845a8 destroyed +### ref @ 0x7fff136845c8 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee95a0 +MyObject1[6] +### ref @ 0x7fff136845c8 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee95a0 +MyObject1[6] +### ref @ 0x7fff136845c8 destroyed 7 - - - - - - -MyObject3[9] destructor +### Object @ 0xee97f0 created via default constructor +### MyObject1 @ 0xee97f0 created MyObject1[7] +### ref @ 0x7f6a2c32ab08 created from pointer 0xee97f0 +MyObject1[7] +### MyObject1 @ 0xee97f0 destroyed +### Object @ 0xee97f0 destroyed +### ref @ 0x7f6a2c32ab08 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### Object @ 0xee99e0 created via default constructor +### MyObject1 @ 0xee99e0 created MyObject1[7] +### ref @ 0x7f6a2c32ab08 created from pointer 0xee99e0 +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee99e0 +### ref @ 0x7fff136845a8 created via copy constructor with pointer 0xee99e0 +MyObject1[7] +### ref @ 0x7fff136845a8 destroyed +### ref @ 0x7fff136845c8 destroyed +### MyObject1 @ 0xee99e0 destroyed +### Object @ 0xee99e0 destroyed +### ref @ 0x7f6a2c32ab08 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### Object @ 0xee97f0 created via default constructor +### MyObject1 @ 0xee97f0 created MyObject1[7] +### ref @ 0x7f6a2c32ab08 created from pointer 0xee97f0 +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee97f0 +MyObject1[7] +### ref @ 0x7fff136845c8 destroyed +### MyObject1 @ 0xee97f0 destroyed +### Object @ 0xee97f0 destroyed +### ref @ 0x7f6a2c32ab08 destroyed +### ref @ 0x7fff136845c8 created via default constructor +### Object @ 0xee99e0 created via default constructor +### MyObject1 @ 0xee99e0 created MyObject1[7] +### ref @ 0x7f6a2c32ab08 created from pointer 0xee99e0 +### ref @ 0x7fff136845c8 assigned via copy assignment pointer 0xee99e0 +MyObject1[7] +### ref @ 0x7fff136845c8 destroyed +### MyObject1 @ 0xee99e0 destroyed +### Object @ 0xee99e0 destroyed +### ref @ 0x7f6a2c32ab08 destroyed +### MyObject1 @ 0xee95a0 destroyed +### Object @ 0xee95a0 destroyed +### ref @ 0x7f6a2c32ab38 destroyed +### MyObject1 @ 0xee8470 destroyed +### Object @ 0xee8470 destroyed +### ref @ 0x7f6a2c32aad8 destroyed +### MyObject1 @ 0xee8310 destroyed +### Object @ 0xee8310 destroyed +### ref @ 0x7f6a2e03c4a8 destroyed +### MyObject2 @ 0xe43f50 created MyObject2[8] +### MyObject2 @ 0xee95a0 created MyObject2[6] +### MyObject2 @ 0xee95d0 created MyObject2[7] + +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 @ 0xee95a0 destroyed +### MyObject2 @ 0xe43f50 destroyed +### MyObject3 @ 0xee9ac0 created MyObject3[9] +### MyObject3 @ 0xe43f90 created MyObject3[8] +### MyObject3 @ 0xeea7d0 created MyObject3[9] +### MyObject2 @ 0xee95d0 destroyed + +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 @ 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] diff --git a/example/example-virtual-functions.cpp b/example/example-virtual-functions.cpp index 55a087109..5d2c9c32d 100644 --- a/example/example-virtual-functions.cpp +++ b/example/example-virtual-functions.cpp @@ -8,18 +8,16 @@ */ #include "example.h" +#include "constructor-stats.h" #include /* This is an example class that we'll want to be able to extend from Python */ class ExampleVirt { public: - ExampleVirt(int state) : state(state) { - cout << "Constructing ExampleVirt.." << endl; - } - - ~ExampleVirt() { - cout << "Destructing ExampleVirt.." << endl; - } + ExampleVirt(int state) : state(state) { print_created(this, state); } + 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); } virtual int run(int value) { std::cout << "Original implementation of ExampleVirt::run(state=" << state @@ -71,8 +69,8 @@ public: class NonCopyable { public: - NonCopyable(int a, int b) : value{new int(a*b)} {} - NonCopyable(NonCopyable &&) = default; + NonCopyable(int a, int b) : value{new int(a*b)} { print_created(this, a, b); } + NonCopyable(NonCopyable &&o) { value = std::move(o.value); print_move_created(this); } NonCopyable(const NonCopyable &) = delete; NonCopyable() = delete; void operator=(const NonCopyable &) = delete; @@ -80,7 +78,7 @@ public: std::string get_value() const { 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: std::unique_ptr value; @@ -90,11 +88,11 @@ private: // when it is not referenced elsewhere, but copied if it is still referenced. class Movable { public: - Movable(int a, int b) : value{a+b} {} - Movable(const Movable &m) { value = m.value; std::cout << "Movable @ " << this << " copy constructor" << std::endl; } - Movable(Movable &&m) { value = std::move(m.value); std::cout << "Movable @ " << this << " move constructor" << std::endl; } + Movable(int a, int b) : value{a+b} { print_created(this, a, b); } + Movable(const Movable &m) { value = m.value; print_copy_created(this); } + Movable(Movable &&m) { value = std::move(m.value); print_move_created(this); } int get_value() const { return value; } - ~Movable() { std::cout << "Movable destructor @ " << this << "; value = " << get_value() << std::endl; } + ~Movable() { print_destroyed(this); } private: int value; }; @@ -305,5 +303,6 @@ void init_ex_virtual_functions(py::module &m) { m.def("runExampleVirtBool", &runExampleVirtBool); m.def("runExampleVirtVirtual", &runExampleVirtVirtual); + m.def("cstats_debug", &ConstructorStats::get); initialize_inherited_virtuals(m); } diff --git a/example/example-virtual-functions.py b/example/example-virtual-functions.py index 121f330c8..d67539b65 100644 --- a/example/example-virtual-functions.py +++ b/example/example-virtual-functions.py @@ -37,8 +37,6 @@ print(runExampleVirt(ex12p, 20)) print(runExampleVirtBool(ex12p)) runExampleVirtVirtual(ex12p) -sys.stdout.flush() - class VI_AR(A_Repeat): def unlucky_number(self): return 99 @@ -122,3 +120,16 @@ try: except RuntimeError as e: # Don't print the exception message here because it differs under debug/non-debug mode 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))]) + diff --git a/example/example-virtual-functions.ref b/example/example-virtual-functions.ref index c5c9223de..5927e0d67 100644 --- a/example/example-virtual-functions.ref +++ b/example/example-virtual-functions.ref @@ -1,8 +1,8 @@ -Constructing ExampleVirt.. +### ExampleVirt @ 0x2073a90 created 10 Original implementation of ExampleVirt::run(state=10, value=20) 30 Caught expected exception: Tried to call pure virtual function "ExampleVirt::pure_virtual" -Constructing ExampleVirt.. +### ExampleVirt @ 0x2076a00 created 11 ExtendedExampleVirt::run(20), calling parent.. Original implementation of ExampleVirt::run(state=11, value=21) 32 @@ -78,20 +78,29 @@ VI_DT says: quack quack quack Unlucky = 1234 Lucky = -4.25 2^2 * 3^2 = -NonCopyable destructor @ 0x1a6c3f0; value = (null) +### NonCopyable @ 0x207df10 created 4 9 +### NonCopyable @ 0x7ffcfe866228 created via move constructor +### NonCopyable @ 0x207df10 destroyed 36 -NonCopyable destructor @ 0x7ffc6d1fbaa8; value = 36 +### NonCopyable @ 0x7ffcfe866228 destroyed 4 + 5 = -Movable @ 0x7ffc6d1fbacc copy constructor +### Movable @ 0x207e230 created 4 5 +### Movable @ 0x7ffcfe86624c created via copy constructor 9 -Movable destructor @ 0x7ffc6d1fbacc; value = 9 +### Movable @ 0x7ffcfe86624c destroyed 7 + 7 = -Movable @ 0x7ffc6d1fbacc move constructor -Movable destructor @ 0x1a6c4d0; value = 14 +### Movable @ 0x20259e0 created 7 7 +### Movable @ 0x7ffcfe86624c created via move constructor +### Movable @ 0x20259e0 destroyed 14 -Movable destructor @ 0x7ffc6d1fbacc; value = 14 +### Movable @ 0x7ffcfe86624c destroyed +### NonCopyable @ 0x2025a00 created 9 9 Caught expected exception -NonCopyable destructor @ 0x29a64b0; value = 81 -Movable destructor @ 0x1a6c410; value = 9 -Destructing ExampleVirt.. -Destructing ExampleVirt.. +### ExampleVirt @ 0x2073a90 destroyed +### ExampleVirt @ 0x2076a00 destroyed +### Movable @ 0x207e230 destroyed +### 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] diff --git a/example/example.cpp b/example/example.cpp index e62fde72d..b831e3e84 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -8,6 +8,7 @@ */ #include "example.h" +#include "constructor-stats.h" void init_ex_methods_and_attributes(py::module &); void init_ex_python_types(py::module &); @@ -34,9 +35,24 @@ void init_issues(py::module &); void init_eigen(py::module &); #endif +void bind_ConstructorStats(py::module &m) { + py::class_(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) { py::module m("example", "pybind example plugin"); + bind_ConstructorStats(m); + init_ex_methods_and_attributes(m); init_ex_python_types(m); init_ex_operator_overloading(m); diff --git a/example/issues.cpp b/example/issues.cpp index 032b6ddf2..7dfa9f5aa 100644 --- a/example/issues.cpp +++ b/example/issues.cpp @@ -8,11 +8,18 @@ */ #include "example.h" +#include "constructor-stats.h" #include #include PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); +#define TRACKERS(CLASS) CLASS() { print_default_created(this); } ~CLASS() { print_destroyed(this); } +struct NestABase { int value = -2; TRACKERS(NestABase) }; +struct NestA : NestABase { int value = 3; NestA& operator+=(int i) { value += i; return *this; } TRACKERS(NestA) }; +struct NestB { NestA a; int value = 4; NestB& operator-=(int i) { value -= i; return *this; } TRACKERS(NestB) }; +struct NestC { NestB b; int value = 5; NestC& operator*=(int i) { value *= i; return *this; } TRACKERS(NestC) }; + void init_issues(py::module &m) { py::module m2 = m.def_submodule("issues"); @@ -159,12 +166,6 @@ void init_issues(py::module &m) { ; // 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_(m2, "NestABase").def(py::init<>()).def_readwrite("value", &NestABase::value); py::class_(m2, "NestA").def(py::init<>()).def(py::self += int()) .def("as_base", [](NestA &a) -> NestABase& { return (NestABase&) a; }, py::return_value_policy::reference_internal); diff --git a/example/issues.ref b/example/issues.ref index 8a05c5dc2..1d5d0fbc8 100644 --- a/example/issues.ref +++ b/example/issues.ref @@ -24,15 +24,15 @@ Failed as expected: Incompatible constructor arguments. The following argument t 1. example.issues.StrIssue(arg0: int) 2. example.issues.StrIssue() Invoked with: no, such, constructor -NestABase@0x1152940 constructor -NestA@0x1152940 constructor -NestABase@0x11f9350 constructor -NestA@0x11f9350 constructor -NestB@0x11f9350 constructor -NestABase@0x112d0d0 constructor -NestA@0x112d0d0 constructor -NestB@0x112d0d0 constructor -NestC@0x112d0d0 constructor +### NestABase @ 0x15eb630 created via default constructor +### NestA @ 0x15eb630 created via default constructor +### NestABase @ 0x1704000 created via default constructor +### NestA @ 0x1704000 created via default constructor +### NestB @ 0x1704000 created via default constructor +### NestABase @ 0x1633110 created via default constructor +### NestA @ 0x1633110 created via default constructor +### NestB @ 0x1633110 created via default constructor +### NestC @ 0x1633110 created via default constructor 13 103 1003 @@ -43,13 +43,13 @@ NestC@0x112d0d0 constructor 42 -2 42 -NestC@0x112d0d0 destructor -NestB@0x112d0d0 destructor -NestA@0x112d0d0 destructor -NestABase@0x112d0d0 destructor +### NestC @ 0x1633110 destroyed +### NestB @ 0x1633110 destroyed +### NestA @ 0x1633110 destroyed +### NestABase @ 0x1633110 destroyed 42 -NestA@0x1152940 destructor -NestABase@0x1152940 destructor -NestB@0x11f9350 destructor -NestA@0x11f9350 destructor -NestABase@0x11f9350 destructor +### NestA @ 0x15eb630 destroyed +### NestABase @ 0x15eb630 destroyed +### NestB @ 0x1704000 destroyed +### NestA @ 0x1704000 destroyed +### NestABase @ 0x1704000 destroyed diff --git a/example/object.h b/example/object.h index 8097bd671..4e6fa6e61 100644 --- a/example/object.h +++ b/example/object.h @@ -2,15 +2,16 @@ #define __OBJECT_H #include +#include "constructor-stats.h" /// Reference counted object base class class Object { public: /// Default constructor - Object() { } + Object() { print_default_created(this); } /// Copy constructor - Object(const Object &) : m_refCount(0) {} + Object(const Object &) : m_refCount(0) { print_copy_created(this); } /// Return the current reference count int getRefCount() const { return m_refCount; }; @@ -37,11 +38,17 @@ protected: /** \brief Virtual protected deconstructor. * (Will only be called by \ref ref) */ - virtual ~Object() { } + virtual ~Object() { print_destroyed(this); } private: mutable std::atomic 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), and *also* add a fake tracker for +// ref_tag. This lets us check that the total number of ref constructors/destructors is +// correct without having to check each individual ref type individually. +class ref_tag {}; + /** * \brief Reference counting helper * @@ -55,37 +62,43 @@ private: template class ref { public: /// 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 ref(T *ptr) : m_ptr(ptr) { - std::cout << "Initialized ref from pointer " << ptr<< std::endl; if (m_ptr) ((Object *) m_ptr)->incRef(); + + print_created(this, "from pointer", m_ptr); track_created((ref_tag*) this, "from pointer"); + } /// Copy constructor ref(const ref &r) : m_ptr(r.m_ptr) { - std::cout << "Initialized ref from ref " << r.m_ptr << std::endl; if (m_ptr) ((Object *) m_ptr)->incRef(); + + print_copy_created(this, "with pointer", m_ptr); track_copy_created((ref_tag*) this); } /// Move constructor 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; + + print_move_created(this, "with pointer", m_ptr); track_move_created((ref_tag*) this); } /// Destroy this reference ~ref() { - std::cout << "Destructing ref " << m_ptr << std::endl; if (m_ptr) ((Object *) m_ptr)->decRef(); + + print_destroyed(this); track_destroyed((ref_tag*) this); } /// Move another reference into the current one 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) return *this; if (m_ptr) @@ -97,7 +110,8 @@ public: /// Overwrite this reference with another reference 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) return *this; if (m_ptr) @@ -110,7 +124,8 @@ public: /// Overwrite this reference with a pointer to another object 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) return *this; if (m_ptr) diff --git a/example/run_test.py b/example/run_test.py index e3170281d..a11c3fc68 100755 --- a/example/run_test.py +++ b/example/run_test.py @@ -9,14 +9,16 @@ remove_long_marker = re.compile(r'([0-9])L') remove_hex = re.compile(r'0x[0-9a-fA-F]+') shorten_floats = re.compile(r'([1-9][0-9]*\.[0-9]{4})[0-9]*') -relaxed = False - def sanitize(lines): lines = lines.split('\n') for i in range(len(lines)): line = lines[i] if line.startswith(" |"): 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_long_marker.sub(r'\1', line) line = remove_hex.sub(r'0', line) @@ -28,13 +30,6 @@ def sanitize(lines): line = line.replace('example.EMode', 'EMode') line = line.replace('method of builtins.PyCapsule instance', '') 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 return '\n'.join(sorted([l for l in lines if l != ""])) @@ -44,16 +39,12 @@ if path != '': os.chdir(path) if len(sys.argv) < 2: - print("Syntax: %s [--relaxed] " % sys.argv[0]) + print("Syntax: %s " % sys.argv[0]) exit(0) -if len(sys.argv) == 3 and sys.argv[1] == '--relaxed': - del sys.argv[1] - relaxed = True - name = sys.argv[1] try: - output_bytes = subprocess.check_output([sys.executable, name + ".py"], + output_bytes = subprocess.check_output([sys.executable, "-u", name + ".py"], stderr=subprocess.STDOUT) except subprocess.CalledProcessError as e: if e.returncode == 99: