From 3f589379ec04c2c2a677ffc060418db151c61cd0 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Sun, 7 Aug 2016 13:05:26 -0400 Subject: [PATCH] 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. --- example/CMakeLists.txt | 3 - example/constructor-stats.h | 243 ++++++++++ example/example-buffers.cpp | 16 +- example/example-buffers.py | 13 + example/example-buffers.ref | 15 +- example/example-callbacks.cpp | 33 +- example/example-callbacks.py | 6 + example/example-callbacks.ref | 20 +- example/example-methods-and-attributes.cpp | 32 +- example/example-methods-and-attributes.py | 13 + example/example-methods-and-attributes.ref | 32 +- example/example-modules.cpp | 15 +- example/example-modules.py | 13 + example/example-modules.ref | 29 +- example/example-opaque-types.py | 2 + example/example-opaque-types.ref | 9 +- example/example-operator-overloading.cpp | 17 +- example/example-operator-overloading.py | 14 + example/example-operator-overloading.ref | 97 ++-- example/example-python-types.cpp | 12 +- example/example-python-types.py | 7 + example/example-python-types.ref | 5 +- example/example-sequences-and-iterators.cpp | 29 +- example/example-sequences-and-iterators.py | 16 + example/example-sequences-and-iterators.ref | 38 +- example/example-smart-ptr.cpp | 15 +- example/example-smart-ptr.py | 15 + example/example-smart-ptr.ref | 505 +++++++++++--------- example/example-virtual-functions.cpp | 27 +- example/example-virtual-functions.py | 15 +- example/example-virtual-functions.ref | 35 +- example/example.cpp | 16 + example/issues.cpp | 13 +- example/issues.ref | 36 +- example/object.h | 37 +- example/run_test.py | 21 +- 36 files changed, 969 insertions(+), 495 deletions(-) create mode 100644 example/constructor-stats.h 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: