Commit f4f2afb6 by Wenzel Jakob Committed by GitHub

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

Improve constructor/destructor tracking
parents 85557b1d 3f589379
...@@ -60,9 +60,6 @@ foreach(CompilerFlag ${CompilerFlags}) ...@@ -60,9 +60,6 @@ foreach(CompilerFlag ${CompilerFlags})
endforeach() endforeach()
set(RUN_TEST ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/run_test.py) set(RUN_TEST ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/run_test.py)
if(MSVC OR CMAKE_CXX_COMPILER_ID MATCHES "Intel")
set(RUN_TEST ${RUN_TEST} --relaxed)
endif()
foreach(VALUE ${PYBIND11_EXAMPLES}) foreach(VALUE ${PYBIND11_EXAMPLES})
string(REGEX REPLACE "^(.+).cpp$" "\\1" EXAMPLE_NAME "${VALUE}") string(REGEX REPLACE "^(.+).cpp$" "\\1" EXAMPLE_NAME "${VALUE}")
......
...@@ -8,35 +8,36 @@ ...@@ -8,35 +8,36 @@
*/ */
#include "example.h" #include "example.h"
#include "constructor-stats.h"
class Matrix { class Matrix {
public: public:
Matrix(size_t rows, size_t cols) : m_rows(rows), m_cols(cols) { Matrix(size_t rows, size_t cols) : m_rows(rows), m_cols(cols) {
std::cout << "Value constructor: Creating a " << rows << "x" << cols << " matrix " << std::endl; print_created(this, std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix");
m_data = new float[rows*cols]; m_data = new float[rows*cols];
memset(m_data, 0, sizeof(float) * rows * cols); memset(m_data, 0, sizeof(float) * rows * cols);
} }
Matrix(const Matrix &s) : m_rows(s.m_rows), m_cols(s.m_cols) { Matrix(const Matrix &s) : m_rows(s.m_rows), m_cols(s.m_cols) {
std::cout << "Copy constructor: Creating a " << m_rows << "x" << m_cols << " matrix " << std::endl; print_copy_created(this, std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix");
m_data = new float[m_rows * m_cols]; m_data = new float[m_rows * m_cols];
memcpy(m_data, s.m_data, sizeof(float) * m_rows * m_cols); memcpy(m_data, s.m_data, sizeof(float) * m_rows * m_cols);
} }
Matrix(Matrix &&s) : m_rows(s.m_rows), m_cols(s.m_cols), m_data(s.m_data) { Matrix(Matrix &&s) : m_rows(s.m_rows), m_cols(s.m_cols), m_data(s.m_data) {
std::cout << "Move constructor: Creating a " << m_rows << "x" << m_cols << " matrix " << std::endl; print_move_created(this);
s.m_rows = 0; s.m_rows = 0;
s.m_cols = 0; s.m_cols = 0;
s.m_data = nullptr; s.m_data = nullptr;
} }
~Matrix() { ~Matrix() {
std::cout << "Freeing a " << m_rows << "x" << m_cols << " matrix " << std::endl; print_destroyed(this, std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix");
delete[] m_data; delete[] m_data;
} }
Matrix &operator=(const Matrix &s) { Matrix &operator=(const Matrix &s) {
std::cout << "Assignment operator : Creating a " << s.m_rows << "x" << s.m_cols << " matrix " << std::endl; print_copy_assigned(this, std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix");
delete[] m_data; delete[] m_data;
m_rows = s.m_rows; m_rows = s.m_rows;
m_cols = s.m_cols; m_cols = s.m_cols;
...@@ -46,7 +47,7 @@ public: ...@@ -46,7 +47,7 @@ public:
} }
Matrix &operator=(Matrix &&s) { Matrix &operator=(Matrix &&s) {
std::cout << "Move assignment operator : Creating a " << s.m_rows << "x" << s.m_cols << " matrix " << std::endl; print_move_assigned(this, std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix");
if (&s != this) { if (&s != this) {
delete[] m_data; delete[] m_data;
m_rows = s.m_rows; m_cols = s.m_cols; m_data = s.m_data; m_rows = s.m_rows; m_cols = s.m_cols; m_data = s.m_data;
...@@ -111,5 +112,6 @@ void init_ex_buffers(py::module &m) { ...@@ -111,5 +112,6 @@ void init_ex_buffers(py::module &m) {
{ sizeof(float) * m.rows(), /* Strides (in bytes) for each index */ { sizeof(float) * m.rows(), /* Strides (in bytes) for each index */
sizeof(float) } sizeof(float) }
); );
}); })
;
} }
...@@ -30,3 +30,16 @@ for i in range(m4.rows()): ...@@ -30,3 +30,16 @@ for i in range(m4.rows()):
for j in range(m4.cols()): for j in range(m4.cols()):
print(m4[i, j], end = ' ') print(m4[i, j], end = ' ')
print() print()
from example import ConstructorStats
cstats = ConstructorStats.get(Matrix)
print("Instances not destroyed:", cstats.alive())
m = m4 = None
print("Instances not destroyed:", cstats.alive())
m2 = None # m2 holds an m reference
print("Instances not destroyed:", cstats.alive())
print("Constructor values:", cstats.values())
print("Copy constructions:", cstats.copy_constructions)
#print("Move constructions:", cstats.move_constructions >= 0) # Don't invoke any
print("Copy assignments:", cstats.copy_assignments)
print("Move assignments:", cstats.move_assignments)
Value constructor: Creating a 5x5 matrix ### Matrix @ 0x1df1920 created 5x5 matrix
0.0 0.0
4.0 4.0
[[ 0. 0. 0. 0. 0.] [[ 0. 0. 0. 0. 0.]
...@@ -10,8 +10,15 @@ Value constructor: Creating a 5x5 matrix ...@@ -10,8 +10,15 @@ Value constructor: Creating a 5x5 matrix
5.0 5.0
[[ 1. 2. 3.] [[ 1. 2. 3.]
[ 4. 5. 6.]] [ 4. 5. 6.]]
Value constructor: Creating a 2x3 matrix ### Matrix @ 0x1fa8cf0 created 2x3 matrix
1.0 2.0 3.0 1.0 2.0 3.0
4.0 5.0 6.0 4.0 5.0 6.0
Freeing a 2x3 matrix Instances not destroyed: 2
Freeing a 5x5 matrix ### Matrix @ 0x1fa8cf0 destroyed 2x3 matrix
Instances not destroyed: 1
### Matrix @ 0x1df1920 destroyed 5x5 matrix
Instances not destroyed: 0
Constructor values: ['5x5 matrix', '2x3 matrix']
Copy constructions: 0
Copy assignments: 0
Move assignments: 0
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
*/ */
#include "example.h" #include "example.h"
#include "constructor-stats.h"
#include <pybind11/functional.h> #include <pybind11/functional.h>
...@@ -57,29 +58,29 @@ void test_dummy_function(const std::function<int(int)> &f) { ...@@ -57,29 +58,29 @@ void test_dummy_function(const std::function<int(int)> &f) {
} }
} }
void init_ex_callbacks(py::module &m) { struct Payload {
m.def("test_callback1", &test_callback1);
m.def("test_callback2", &test_callback2);
m.def("test_callback3", &test_callback3);
m.def("test_callback4", &test_callback4);
m.def("test_callback5", &test_callback5);
/* Test cleanup of lambda closure */
struct Payload {
Payload() { Payload() {
std::cout << "Payload constructor" << std::endl; print_default_created(this);
} }
~Payload() { ~Payload() {
std::cout << "Payload destructor" << std::endl; print_destroyed(this);
} }
Payload(const Payload &) { Payload(const Payload &) {
std::cout << "Payload copy constructor" << std::endl; print_copy_created(this);
} }
Payload(Payload &&) { Payload(Payload &&) {
std::cout << "Payload move constructor" << std::endl; print_move_created(this);
} }
}; };
void init_ex_callbacks(py::module &m) {
m.def("test_callback1", &test_callback1);
m.def("test_callback2", &test_callback2);
m.def("test_callback3", &test_callback3);
m.def("test_callback4", &test_callback4);
m.def("test_callback5", &test_callback5);
/* Test cleanup of lambda closure */
m.def("test_cleanup", []() -> std::function<void(void)> { m.def("test_cleanup", []() -> std::function<void(void)> {
Payload p; Payload p;
...@@ -94,4 +95,6 @@ void init_ex_callbacks(py::module &m) { ...@@ -94,4 +95,6 @@ void init_ex_callbacks(py::module &m) {
m.def("dummy_function2", &dummy_function2); m.def("dummy_function2", &dummy_function2);
m.def("roundtrip", &roundtrip); m.def("roundtrip", &roundtrip);
m.def("test_dummy_function", &test_dummy_function); m.def("test_dummy_function", &test_dummy_function);
// Export the payload constructor statistics for testing purposes:
m.def("payload_cstats", &ConstructorStats::get<Payload>);
} }
...@@ -55,6 +55,12 @@ print("func(number=43) = %i" % f(number=43)) ...@@ -55,6 +55,12 @@ print("func(number=43) = %i" % f(number=43))
test_cleanup() test_cleanup()
from example import payload_cstats
cstats = payload_cstats()
print("Payload instances not destroyed:", cstats.alive())
print("Copy constructions:", cstats.copy_constructions)
print("Move constructions:", cstats.move_constructions >= 1)
from example import dummy_function from example import dummy_function
from example import dummy_function2 from example import dummy_function2
from example import test_dummy_function from example import test_dummy_function
......
...@@ -7,7 +7,7 @@ Molly is a dog ...@@ -7,7 +7,7 @@ Molly is a dog
Woof! Woof!
The following error is expected: Incompatible function arguments. The following argument types are supported: The following error is expected: Incompatible function arguments. The following argument types are supported:
1. (arg0: example.Dog) -> None 1. (arg0: example.Dog) -> None
Invoked with: <example.Pet object at 0> Invoked with: <example.Pet object at 0x7ffaf4b00db0>
Callback function 1 called! Callback function 1 called!
False False
Callback function 2 called : Hello, x, True, 5 Callback function 2 called : Hello, x, True, 5
...@@ -19,12 +19,15 @@ False ...@@ -19,12 +19,15 @@ False
func(43) = 44 func(43) = 44
func(43) = 44 func(43) = 44
func(number=43) = 44 func(number=43) = 44
Payload constructor ### Payload @ 0x7ffdcee09c80 created via default constructor
Payload copy constructor ### Payload @ 0x7ffdcee09c88 created via copy constructor
Payload move constructor ### Payload @ 0xb54500 created via move constructor
Payload destructor ### Payload @ 0x7ffdcee09c88 destroyed
Payload destructor ### Payload @ 0x7ffdcee09c80 destroyed
Payload destructor ### Payload @ 0xb54500 destroyed
Payload instances not destroyed: 0
Copy constructions: 1
Move constructions: True
argument matches dummy_function argument matches dummy_function
eval(1) = 2 eval(1) = 2
roundtrip.. roundtrip..
...@@ -36,6 +39,7 @@ could not convert to a function pointer. ...@@ -36,6 +39,7 @@ could not convert to a function pointer.
All OK! All OK!
could not convert to a function pointer. could not convert to a function pointer.
All OK! All OK!
test_callback3(arg0: Callable[[int], int]) -> None test_callback3(arg0: Callable[[int], int]) -> None
test_callback4() -> Callable[[int], int] test_callback4() -> Callable[[int], int]
...@@ -8,32 +8,25 @@ ...@@ -8,32 +8,25 @@
BSD-style license that can be found in the LICENSE file. BSD-style license that can be found in the LICENSE file.
*/ */
#include <unordered_map>
#include <list>
#include "example.h" #include "example.h"
#include "constructor-stats.h"
class ExampleMandA { class ExampleMandA {
public: public:
ExampleMandA() { ExampleMandA() { print_default_created(this); }
cout << "Called ExampleMandA default constructor.." << endl; ExampleMandA(int value) : value(value) { print_created(this, value); }
} ExampleMandA(const ExampleMandA &e) : value(e.value) { print_copy_created(this); }
ExampleMandA(int value) : value(value) { ExampleMandA(ExampleMandA &&e) : value(e.value) { print_move_created(this); }
cout << "Called ExampleMandA constructor with value " << value << ".." << endl; ~ExampleMandA() { print_destroyed(this); }
}
ExampleMandA(const ExampleMandA &e) : value(e.value) {
cout << "Called ExampleMandA copy constructor with value " << value << ".." << endl;
}
ExampleMandA(ExampleMandA &&e) : value(e.value) {
cout << "Called ExampleMandA move constructor with value " << value << ".." << endl;
e.value = 0;
}
~ExampleMandA() {
cout << "Called ExampleMandA destructor (" << value << ")" << endl;
}
std::string toString() { std::string toString() {
return "ExampleMandA[value=" + std::to_string(value) + "]"; return "ExampleMandA[value=" + std::to_string(value) + "]";
} }
void operator=(const ExampleMandA &e) { cout << "Assignment operator" << endl; value = e.value; } void operator=(const ExampleMandA &e) { print_copy_assigned(this); value = e.value; }
void operator=(ExampleMandA &&e) { cout << "Move assignment operator" << endl; value = e.value; e.value = 0;} void operator=(ExampleMandA &&e) { print_move_assigned(this); value = e.value; }
void add1(ExampleMandA other) { value += other.value; } // passing by value void add1(ExampleMandA other) { value += other.value; } // passing by value
void add2(ExampleMandA &other) { value += other.value; } // passing by reference void add2(ExampleMandA &other) { value += other.value; } // passing by reference
...@@ -88,5 +81,6 @@ void init_ex_methods_and_attributes(py::module &m) { ...@@ -88,5 +81,6 @@ void init_ex_methods_and_attributes(py::module &m) {
.def("internal4", &ExampleMandA::internal4) .def("internal4", &ExampleMandA::internal4)
.def("internal5", &ExampleMandA::internal5) .def("internal5", &ExampleMandA::internal5)
.def("__str__", &ExampleMandA::toString) .def("__str__", &ExampleMandA::toString)
.def_readwrite("value", &ExampleMandA::value); .def_readwrite("value", &ExampleMandA::value)
;
} }
...@@ -35,3 +35,16 @@ print(instance1.internal5()) ...@@ -35,3 +35,16 @@ print(instance1.internal5())
print("Instance 1, direct access = %i" % instance1.value) print("Instance 1, direct access = %i" % instance1.value)
instance1.value = 100 instance1.value = 100
print("Instance 1: " + str(instance1)) print("Instance 1: " + str(instance1))
from example import ConstructorStats
cstats = ConstructorStats.get(ExampleMandA)
print("Instances not destroyed:", cstats.alive())
instance1 = instance2 = None
print("Instances not destroyed:", cstats.alive())
print("Constructor values:", cstats.values())
print("Default constructions:", cstats.default_constructions)
print("Copy constructions:", cstats.copy_constructions)
print("Move constructions:", cstats.move_constructions >= 1)
print("Copy assignments:", cstats.copy_assignments)
print("Move assignments:", cstats.move_assignments)
Called ExampleMandA default constructor.. ### ExampleMandA @ 0x2801910 created via default constructor
Called ExampleMandA constructor with value 32.. ### ExampleMandA @ 0x27fa780 created 32
Called ExampleMandA copy constructor with value 32.. ### ExampleMandA @ 0x7fff80a98a74 created via copy constructor
Called ExampleMandA copy constructor with value 32.. ### ExampleMandA @ 0x7fff80a98a78 created via copy constructor
Called ExampleMandA destructor (32) ### ExampleMandA @ 0x7fff80a98a78 destroyed
Called ExampleMandA destructor (32) ### ExampleMandA @ 0x7fff80a98a74 destroyed
Instance 1: ExampleMandA[value=320] Instance 1: ExampleMandA[value=320]
Instance 2: ExampleMandA[value=32] Instance 2: ExampleMandA[value=32]
Called ExampleMandA copy constructor with value 320.. ### ExampleMandA @ 0x7fff80a98a84 created via copy constructor
Called ExampleMandA move constructor with value 320.. ### ExampleMandA @ 0x2801fd0 created via move constructor
Called ExampleMandA destructor (0) ### ExampleMandA @ 0x7fff80a98a84 destroyed
ExampleMandA[value=320] ExampleMandA[value=320]
Called ExampleMandA destructor (320) ### ExampleMandA @ 0x2801fd0 destroyed
ExampleMandA[value=320] ExampleMandA[value=320]
ExampleMandA[value=320] ExampleMandA[value=320]
ExampleMandA[value=320] ExampleMandA[value=320]
...@@ -22,5 +22,13 @@ ExampleMandA[value=320] ...@@ -22,5 +22,13 @@ ExampleMandA[value=320]
320 320
Instance 1, direct access = 320 Instance 1, direct access = 320
Instance 1: ExampleMandA[value=100] Instance 1: ExampleMandA[value=100]
Called ExampleMandA destructor (32) Instances not destroyed: 2
Called ExampleMandA destructor (100) ### ExampleMandA @ 0x2801910 destroyed
### ExampleMandA @ 0x27fa780 destroyed
Instances not destroyed: 0
Constructor values: ['32']
Default constructions: 1
Copy constructions: 3
Move constructions: True
Copy assignments: 0
Move assignments: 0
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
*/ */
#include "example.h" #include "example.h"
#include "constructor-stats.h"
void submodule_func() { void submodule_func() {
std::cout << "submodule_func()" << std::endl; std::cout << "submodule_func()" << std::endl;
...@@ -16,9 +17,10 @@ void submodule_func() { ...@@ -16,9 +17,10 @@ void submodule_func() {
class A { class A {
public: public:
A(int v) : v(v) { std::cout << "A constructor" << std::endl; } A(int v) : v(v) { print_created(this, v); }
~A() { std::cout << "A destructor" << std::endl; } ~A() { print_destroyed(this); }
A(const A&) { std::cout << "A copy constructor" << std::endl; } A(const A&) { print_copy_created(this); }
A& operator=(const A &copy) { print_copy_assigned(this); v = copy.v; return *this; }
std::string toString() { return "A[" + std::to_string(v) + "]"; } std::string toString() { return "A[" + std::to_string(v) + "]"; }
private: private:
int v; int v;
...@@ -26,9 +28,10 @@ private: ...@@ -26,9 +28,10 @@ private:
class B { class B {
public: public:
B() { std::cout << "B constructor" << std::endl; } B() { print_default_created(this); }
~B() { std::cout << "B destructor" << std::endl; } ~B() { print_destroyed(this); }
B(const B&) { std::cout << "B copy constructor" << std::endl; } B(const B&) { print_copy_created(this); }
B& operator=(const B &copy) { print_copy_assigned(this); a1 = copy.a1; a2 = copy.a2; return *this; }
A &get_a1() { return a1; } A &get_a1() { return a1; }
A &get_a2() { return a2; } A &get_a2() { return a2; }
......
...@@ -28,3 +28,16 @@ print(b.get_a2()) ...@@ -28,3 +28,16 @@ print(b.get_a2())
print(b.a2) print(b.a2)
print(OD([(1, 'a'), (2, 'b')])) print(OD([(1, 'a'), (2, 'b')]))
from example import ConstructorStats
cstats = [ConstructorStats.get(A), ConstructorStats.get(B)]
print("Instances not destroyed:", [x.alive() for x in cstats])
b = None
print("Instances not destroyed:", [x.alive() for x in cstats])
print("Constructor values:", [x.values() for x in cstats])
print("Default constructions:", [x.default_constructions for x in cstats])
print("Copy constructions:", [x.copy_constructions for x in cstats])
#print("Move constructions:", [x.move_constructions >= 0 for x in cstats]) # Don't invoke any
print("Copy assignments:", [x.copy_assignments for x in cstats])
print("Move assignments:", [x.move_assignments for x in cstats])
example example
example.submodule example.submodule
submodule_func() submodule_func()
A constructor ### A @ 0x21a5bc0 created 1
A constructor ### A @ 0x21a5bc4 created 2
B constructor ### B @ 0x21a5bc0 created via default constructor
A[1] A[1]
A[1] A[1]
A[2] A[2]
A[2] A[2]
A constructor ### A @ 0x20f93b0 created 42
A destructor ### A @ 0x21a5bc0 assigned via copy assignment
A constructor ### A @ 0x20f93b0 destroyed
A destructor ### A @ 0x20f93d0 created 43
### A @ 0x21a5bc4 assigned via copy assignment
### A @ 0x20f93d0 destroyed
A[42] A[42]
A[42] A[42]
A[43] A[43]
A[43] A[43]
OrderedDict([(1, 'a'), (2, 'b')]) OrderedDict([(1, 'a'), (2, 'b')])
B destructor Instances not destroyed: [2, 1]
A destructor ### B @ 0x21a5bc0 destroyed
A destructor ### A @ 0x21a5bc4 destroyed
### A @ 0x21a5bc0 destroyed
Instances not destroyed: [0, 0]
Constructor values: [['1', '2', '42', '43'], []]
Default constructions: [0, 1]
Copy constructions: [0, 0]
Copy assignments: [2, 0]
Move assignments: [0, 0]
...@@ -34,6 +34,8 @@ print_opaque_list(cvp.stringList) ...@@ -34,6 +34,8 @@ print_opaque_list(cvp.stringList)
print_void_ptr(return_void_ptr()) print_void_ptr(return_void_ptr())
print_void_ptr(ExampleMandA()) # Should also work for other C++ types print_void_ptr(ExampleMandA()) # Should also work for other C++ types
from example import ConstructorStats
print("ExampleMandA still alive:", ConstructorStats.get(ExampleMandA).alive())
try: try:
print_void_ptr([1, 2, 3]) # This should not work print_void_ptr([1, 2, 3]) # This should not work
......
...@@ -6,13 +6,14 @@ Opaque list: [Element 1] ...@@ -6,13 +6,14 @@ Opaque list: [Element 1]
Opaque list: [] Opaque list: []
Opaque list: [Element 1, Element 3] Opaque list: [Element 1, Element 3]
Got void ptr : 0x1234 Got void ptr : 0x1234
Called ExampleMandA default constructor.. ### ExampleMandA @ 0x2ac5370 created via default constructor
Got void ptr : 0x7f9ba0f3c430 Got void ptr : 0x2ac5370
Called ExampleMandA destructor (0) ### ExampleMandA @ 0x2ac5370 destroyed
ExampleMandA still alive: 0
Caught expected exception: Incompatible function arguments. The following argument types are supported: Caught expected exception: Incompatible function arguments. The following argument types are supported:
1. (arg0: capsule) -> None 1. (arg0: capsule) -> None
Invoked with: [1, 2, 3] Invoked with: [1, 2, 3]
None None
Got null str : 0x0 Got null str : 0x0
<example.StringList object at 0x10d3277a0> <example.StringList object at 0x7f7ecde6fc00>
Opaque list: [some value] Opaque list: [some value]
...@@ -8,27 +8,28 @@ ...@@ -8,27 +8,28 @@
*/ */
#include "example.h" #include "example.h"
#include "constructor-stats.h"
#include <pybind11/operators.h> #include <pybind11/operators.h>
class Vector2 { class Vector2 {
public: public:
Vector2(float x, float y) : x(x), y(y) { std::cout << "Value constructor" << std::endl; } Vector2(float x, float y) : x(x), y(y) { print_created(this, toString()); }
Vector2(const Vector2 &v) : x(v.x), y(v.y) { std::cout << "Copy constructor" << std::endl; } Vector2(const Vector2 &v) : x(v.x), y(v.y) { print_copy_created(this); }
Vector2(Vector2 &&v) : x(v.x), y(v.y) { std::cout << "Move constructor" << std::endl; v.x = v.y = 0; } Vector2(Vector2 &&v) : x(v.x), y(v.y) { print_move_created(this); v.x = v.y = 0; }
~Vector2() { std::cout << "Destructor." << std::endl; } ~Vector2() { print_destroyed(this); }
std::string toString() const { std::string toString() const {
return "[" + std::to_string(x) + ", " + std::to_string(y) + "]"; return "[" + std::to_string(x) + ", " + std::to_string(y) + "]";
} }
void operator=(const Vector2 &v) { void operator=(const Vector2 &v) {
cout << "Assignment operator" << endl; print_copy_assigned(this);
x = v.x; x = v.x;
y = v.y; y = v.y;
} }
void operator=(Vector2 &&v) { void operator=(Vector2 &&v) {
cout << "Move assignment operator" << endl; print_move_assigned(this);
x = v.x; y = v.y; v.x = v.y = 0; x = v.x; y = v.y; v.x = v.y = 0;
} }
...@@ -51,7 +52,6 @@ private: ...@@ -51,7 +52,6 @@ private:
float x, y; float x, y;
}; };
void init_ex_operator_overloading(py::module &m) { void init_ex_operator_overloading(py::module &m) {
py::class_<Vector2>(m, "Vector2") py::class_<Vector2>(m, "Vector2")
.def(py::init<float, float>()) .def(py::init<float, float>())
...@@ -69,7 +69,8 @@ void init_ex_operator_overloading(py::module &m) { ...@@ -69,7 +69,8 @@ void init_ex_operator_overloading(py::module &m) {
.def(float() - py::self) .def(float() - py::self)
.def(float() * py::self) .def(float() * py::self)
.def(float() / py::self) .def(float() / py::self)
.def("__str__", &Vector2::toString); .def("__str__", &Vector2::toString)
;
m.attr("Vector") = m.attr("Vector2"); m.attr("Vector") = m.attr("Vector2");
} }
...@@ -25,3 +25,17 @@ v1 += v2 ...@@ -25,3 +25,17 @@ v1 += v2
v1 *= 2 v1 *= 2
print("(v1+v2)*2 = " + str(v1)) print("(v1+v2)*2 = " + str(v1))
from example import ConstructorStats
cstats = ConstructorStats.get(Vector2)
print("Instances not destroyed:", cstats.alive())
v1 = None
print("Instances not destroyed:", cstats.alive())
v2 = None
print("Instances not destroyed:", cstats.alive())
print("Constructor values:", cstats.values())
print("Default constructions:", cstats.default_constructions)
print("Copy constructions:", cstats.copy_constructions)
print("Move constructions:", cstats.move_constructions >= 10)
print("Copy assignments:", cstats.copy_assignments)
print("Move assignments:", cstats.move_assignments)
Value constructor ### Vector2 @ 0x11f7830 created [1.000000, 2.000000]
Value constructor ### Vector2 @ 0x11427c0 created [3.000000, -1.000000]
v1 = [1.000000, 2.000000] v1 = [1.000000, 2.000000]
v2 = [3.000000, -1.000000] v2 = [3.000000, -1.000000]
Value constructor ### Vector2 @ 0x7ffef6b144b8 created [4.000000, 1.000000]
Move constructor ### Vector2 @ 0x11f7e90 created via move constructor
Destructor. ### Vector2 @ 0x7ffef6b144b8 destroyed
Destructor. ### Vector2 @ 0x11f7e90 destroyed
v1+v2 = [4.000000, 1.000000] v1+v2 = [4.000000, 1.000000]
Value constructor ### Vector2 @ 0x7ffef6b144b8 created [-2.000000, 3.000000]
Move constructor ### Vector2 @ 0x11f7e90 created via move constructor
Destructor. ### Vector2 @ 0x7ffef6b144b8 destroyed
Destructor. ### Vector2 @ 0x11f7e90 destroyed
v1-v2 = [-2.000000, 3.000000] v1-v2 = [-2.000000, 3.000000]
Value constructor ### Vector2 @ 0x7ffef6b144c8 created [-7.000000, -6.000000]
Move constructor ### Vector2 @ 0x1115760 created via move constructor
Destructor. ### Vector2 @ 0x7ffef6b144c8 destroyed
Destructor. ### Vector2 @ 0x1115760 destroyed
v1-8 = [-7.000000, -6.000000] v1-8 = [-7.000000, -6.000000]
Value constructor ### Vector2 @ 0x7ffef6b144c8 created [9.000000, 10.000000]
Move constructor ### Vector2 @ 0x1115760 created via move constructor
Destructor. ### Vector2 @ 0x7ffef6b144c8 destroyed
Destructor. ### Vector2 @ 0x1115760 destroyed
v1+8 = [9.000000, 10.000000] v1+8 = [9.000000, 10.000000]
Value constructor ### Vector2 @ 0x7ffef6b144b8 created [8.000000, 16.000000]
Move constructor ### Vector2 @ 0x1115760 created via move constructor
Destructor. ### Vector2 @ 0x7ffef6b144b8 destroyed
Destructor. ### Vector2 @ 0x1115760 destroyed
v1*8 = [8.000000, 16.000000] v1*8 = [8.000000, 16.000000]
Value constructor ### Vector2 @ 0x7ffef6b144a8 created [0.125000, 0.250000]
Move constructor ### Vector2 @ 0x112f150 created via move constructor
Destructor. ### Vector2 @ 0x7ffef6b144a8 destroyed
Destructor. ### Vector2 @ 0x112f150 destroyed
v1/8 = [0.125000, 0.250000] v1/8 = [0.125000, 0.250000]
Value constructor ### Vector2 @ 0x7ffef6b144f8 created [7.000000, 6.000000]
Move constructor ### Vector2 @ 0x112f1b0 created via move constructor
Destructor. ### Vector2 @ 0x7ffef6b144f8 destroyed
Destructor. ### Vector2 @ 0x112f1b0 destroyed
8-v1 = [7.000000, 6.000000] 8-v1 = [7.000000, 6.000000]
Value constructor ### Vector2 @ 0x7ffef6b144f8 created [9.000000, 10.000000]
Move constructor ### Vector2 @ 0x112f1b0 created via move constructor
Destructor. ### Vector2 @ 0x7ffef6b144f8 destroyed
Destructor. ### Vector2 @ 0x112f1b0 destroyed
8+v1 = [9.000000, 10.000000] 8+v1 = [9.000000, 10.000000]
Value constructor ### Vector2 @ 0x7ffef6b144e8 created [8.000000, 16.000000]
Move constructor ### Vector2 @ 0x112f230 created via move constructor
Destructor. ### Vector2 @ 0x7ffef6b144e8 destroyed
Destructor. ### Vector2 @ 0x112f230 destroyed
8*v1 = [8.000000, 16.000000] 8*v1 = [8.000000, 16.000000]
Value constructor ### Vector2 @ 0x7ffef6b144d8 created [8.000000, 4.000000]
Move constructor ### Vector2 @ 0x11fb360 created via move constructor
Destructor. ### Vector2 @ 0x7ffef6b144d8 destroyed
Destructor. ### Vector2 @ 0x11fb360 destroyed
8/v1 = [8.000000, 4.000000] 8/v1 = [8.000000, 4.000000]
(v1+v2)*2 = [8.000000, 2.000000] (v1+v2)*2 = [8.000000, 2.000000]
Destructor. Instances not destroyed: 2
Destructor. ### Vector2 @ 0x11f7830 destroyed
Instances not destroyed: 1
### Vector2 @ 0x11427c0 destroyed
Instances not destroyed: 0
Constructor values: ['[1.000000, 2.000000]', '[3.000000, -1.000000]', '[4.000000, 1.000000]', '[-2.000000, 3.000000]', '[-7.000000, -6.000000]', '[9.000000, 10.000000]', '[8.000000, 16.000000]', '[0.125000, 0.250000]', '[7.000000, 6.000000]', '[9.000000, 10.000000]', '[8.000000, 16.000000]', '[8.000000, 4.000000]']
Default constructions: 0
Copy constructions: 0
Move constructions: True
Copy assignments: 0
Move assignments: 0
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
*/ */
#include "example.h" #include "example.h"
#include "constructor-stats.h"
#include <pybind11/stl.h> #include <pybind11/stl.h>
#ifdef _WIN32 #ifdef _WIN32
...@@ -19,11 +20,11 @@ ...@@ -19,11 +20,11 @@
class ExamplePythonTypes { class ExamplePythonTypes {
public: public:
static ExamplePythonTypes *new_instance() { static ExamplePythonTypes *new_instance() {
return new ExamplePythonTypes(); auto *ptr = new ExamplePythonTypes();
} print_created(ptr, "via new_instance");
~ExamplePythonTypes() { return ptr;
std::cout << "Destructing ExamplePythonTypes" << std::endl;
} }
~ExamplePythonTypes() { print_destroyed(this); }
/* Create and return a Python dictionary */ /* Create and return a Python dictionary */
py::dict get_dict() { py::dict get_dict() {
...@@ -168,5 +169,6 @@ void init_ex_python_types(py::module &m) { ...@@ -168,5 +169,6 @@ void init_ex_python_types(py::module &m) {
.def("throw_exception", &ExamplePythonTypes::throw_exception, "Throw an exception") .def("throw_exception", &ExamplePythonTypes::throw_exception, "Throw an exception")
.def_static("new_instance", &ExamplePythonTypes::new_instance, "Return an instance") .def_static("new_instance", &ExamplePythonTypes::new_instance, "Return an instance")
.def_readwrite_static("value", &ExamplePythonTypes::value, "Static value member") .def_readwrite_static("value", &ExamplePythonTypes::value, "Static value member")
.def_readonly_static("value2", &ExamplePythonTypes::value2, "Static value member (readonly)"); .def_readonly_static("value2", &ExamplePythonTypes::value2, "Static value member (readonly)")
;
} }
...@@ -65,3 +65,10 @@ print("__name__(example.ExamplePythonTypes) = %s" % ExamplePythonTypes.__name__) ...@@ -65,3 +65,10 @@ print("__name__(example.ExamplePythonTypes) = %s" % ExamplePythonTypes.__name__)
print("__module__(example.ExamplePythonTypes) = %s" % ExamplePythonTypes.__module__) print("__module__(example.ExamplePythonTypes) = %s" % ExamplePythonTypes.__module__)
print("__name__(example.ExamplePythonTypes.get_set) = %s" % ExamplePythonTypes.get_set.__name__) print("__name__(example.ExamplePythonTypes.get_set) = %s" % ExamplePythonTypes.get_set.__name__)
print("__module__(example.ExamplePythonTypes.get_set) = %s" % ExamplePythonTypes.get_set.__module__) print("__module__(example.ExamplePythonTypes.get_set) = %s" % ExamplePythonTypes.get_set.__module__)
from example import ConstructorStats
cstats = ConstructorStats.get(ExamplePythonTypes)
print("Instances not destroyed:", cstats.alive())
instance = None
print("Instances not destroyed:", cstats.alive())
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
5 5
example.ExamplePythonTypes: No constructor defined! example.ExamplePythonTypes: No constructor defined!
can't set attribute can't set attribute
### ExamplePythonTypes @ 0x1045b80 created via new_instance
key: key2, value=value2 key: key2, value=value2
key: key, value=value key: key, value=value
key: key, value=value key: key, value=value
...@@ -134,4 +135,6 @@ __name__(example.ExamplePythonTypes) = ExamplePythonTypes ...@@ -134,4 +135,6 @@ __name__(example.ExamplePythonTypes) = ExamplePythonTypes
__module__(example.ExamplePythonTypes) = example __module__(example.ExamplePythonTypes) = example
__name__(example.ExamplePythonTypes.get_set) = get_set __name__(example.ExamplePythonTypes.get_set) = get_set
__module__(example.ExamplePythonTypes.get_set) = example __module__(example.ExamplePythonTypes.get_set) = example
Destructing ExamplePythonTypes Instances not destroyed: 1
### ExamplePythonTypes @ 0x1045b80 destroyed
Instances not destroyed: 0
...@@ -9,51 +9,55 @@ ...@@ -9,51 +9,55 @@
*/ */
#include "example.h" #include "example.h"
#include "constructor-stats.h"
#include <pybind11/operators.h> #include <pybind11/operators.h>
#include <pybind11/stl.h> #include <pybind11/stl.h>
class Sequence { class Sequence {
public: public:
Sequence(size_t size) : m_size(size) { Sequence(size_t size) : m_size(size) {
std::cout << "Value constructor: Creating a sequence with " << m_size << " entries" << std::endl; print_created(this, "of size", m_size);
m_data = new float[size]; m_data = new float[size];
memset(m_data, 0, sizeof(float) * size); memset(m_data, 0, sizeof(float) * size);
} }
Sequence(const std::vector<float> &value) : m_size(value.size()) { Sequence(const std::vector<float> &value) : m_size(value.size()) {
std::cout << "Value constructor: Creating a sequence with " << m_size << " entries" << std::endl; print_created(this, "of size", m_size, "from std::vector");
m_data = new float[m_size]; m_data = new float[m_size];
memcpy(m_data, &value[0], sizeof(float) * m_size); memcpy(m_data, &value[0], sizeof(float) * m_size);
} }
Sequence(const Sequence &s) : m_size(s.m_size) { Sequence(const Sequence &s) : m_size(s.m_size) {
std::cout << "Copy constructor: Creating a sequence with " << m_size << " entries" << std::endl; print_copy_created(this);
m_data = new float[m_size]; m_data = new float[m_size];
memcpy(m_data, s.m_data, sizeof(float)*m_size); memcpy(m_data, s.m_data, sizeof(float)*m_size);
} }
Sequence(Sequence &&s) : m_size(s.m_size), m_data(s.m_data) { Sequence(Sequence &&s) : m_size(s.m_size), m_data(s.m_data) {
std::cout << "Move constructor: Creating a sequence with " << m_size << " entries" << std::endl; print_move_created(this);
s.m_size = 0; s.m_size = 0;
s.m_data = nullptr; s.m_data = nullptr;
} }
~Sequence() { ~Sequence() {
std::cout << "Freeing a sequence with " << m_size << " entries" << std::endl; print_destroyed(this);
delete[] m_data; delete[] m_data;
} }
Sequence &operator=(const Sequence &s) { Sequence &operator=(const Sequence &s) {
std::cout << "Assignment operator: Creating a sequence with " << s.m_size << " entries" << std::endl; if (&s != this) {
delete[] m_data; delete[] m_data;
m_size = s.m_size; m_size = s.m_size;
m_data = new float[m_size]; m_data = new float[m_size];
memcpy(m_data, s.m_data, sizeof(float)*m_size); memcpy(m_data, s.m_data, sizeof(float)*m_size);
}
print_copy_assigned(this);
return *this; return *this;
} }
Sequence &operator=(Sequence &&s) { Sequence &operator=(Sequence &&s) {
std::cout << "Move assignment operator: Creating a sequence with " << s.m_size << " entries" << std::endl;
if (&s != this) { if (&s != this) {
delete[] m_data; delete[] m_data;
m_size = s.m_size; m_size = s.m_size;
...@@ -61,6 +65,9 @@ public: ...@@ -61,6 +65,9 @@ public:
s.m_size = 0; s.m_size = 0;
s.m_data = nullptr; s.m_data = nullptr;
} }
print_move_assigned(this);
return *this; return *this;
} }
......
...@@ -28,3 +28,19 @@ rev[0::2] = Sequence([2.0, 2.0, 2.0]) ...@@ -28,3 +28,19 @@ rev[0::2] = Sequence([2.0, 2.0, 2.0])
for i in rev: for i in rev:
print(i, end=' ') print(i, end=' ')
print('') print('')
from example import ConstructorStats
cstats = ConstructorStats.get(Sequence)
print("Instances not destroyed:", cstats.alive())
s = None
print("Instances not destroyed:", cstats.alive())
rev = None
print("Instances not destroyed:", cstats.alive())
rev2 = None
print("Instances not destroyed:", cstats.alive())
print("Constructor values:", cstats.values())
print("Default constructions:", cstats.default_constructions)
print("Copy constructions:", cstats.copy_constructions)
print("Move constructions:", cstats.move_constructions >= 1)
print("Copy assignments:", cstats.copy_assignments)
print("Move assignments:", cstats.move_assignments)
Value constructor: Creating a sequence with 5 entries ### Sequence @ 0x1535b00 created of size 5
s = <example.Sequence object at 0x10c786c70> s = <example.Sequence object at 0x7efc73cfa4e0>
len(s) = 5 len(s) = 5
s[0], s[3] = 0.000000 0.000000 s[0], s[3] = 0.000000 0.000000
12.34 in s: False 12.34 in s: False
12.34 in s: True 12.34 in s: True
s[0], s[3] = 12.340000 56.779999 s[0], s[3] = 12.340000 56.779999
Value constructor: Creating a sequence with 5 entries ### Sequence @ 0x7fff22a45068 created of size 5
Move constructor: Creating a sequence with 5 entries ### Sequence @ 0x1538b90 created via move constructor
Freeing a sequence with 0 entries ### Sequence @ 0x7fff22a45068 destroyed
Value constructor: Creating a sequence with 5 entries ### Sequence @ 0x1538bf0 created of size 5
rev[0], rev[1], rev[2], rev[3], rev[4] = 0.000000 56.779999 0.000000 0.000000 12.340000 rev[0], rev[1], rev[2], rev[3], rev[4] = 0.000000 56.779999 0.000000 0.000000 12.340000
0.0 56.7799987793 0.0 0.0 12.3400001526 0.0 56.779998779296875 0.0 0.0 12.34000015258789
0.0 56.7799987793 0.0 0.0 12.3400001526 0.0 56.779998779296875 0.0 0.0 12.34000015258789
True True
Value constructor: Creating a sequence with 3 entries ### Sequence @ 0x153c4b0 created of size 3 from std::vector
Freeing a sequence with 3 entries ### Sequence @ 0x153c4b0 destroyed
2.0 56.7799987793 2.0 0.0 2.0 2.0 56.779998779296875 2.0 0.0 2.0
Freeing a sequence with 5 entries Instances not destroyed: 3
Freeing a sequence with 5 entries ### Sequence @ 0x1535b00 destroyed
Freeing a sequence with 5 entries Instances not destroyed: 2
### Sequence @ 0x1538b90 destroyed
Instances not destroyed: 1
### Sequence @ 0x1538bf0 destroyed
Instances not destroyed: 0
Constructor values: ['of size', '5', 'of size', '5', 'of size', '5', 'of size', '3', 'from std::vector']
Default constructions: 0
Copy constructions: 0
Move constructions: True
Copy assignments: 0
Move assignments: 0
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
class MyObject1 : public Object { class MyObject1 : public Object {
public: public:
MyObject1(int value) : value(value) { MyObject1(int value) : value(value) {
std::cout << toString() << " constructor" << std::endl; print_created(this, toString());
} }
std::string toString() const { std::string toString() const {
...@@ -24,7 +24,7 @@ public: ...@@ -24,7 +24,7 @@ public:
protected: protected:
virtual ~MyObject1() { virtual ~MyObject1() {
std::cout << toString() << " destructor" << std::endl; print_destroyed(this);
} }
private: private:
...@@ -35,7 +35,7 @@ private: ...@@ -35,7 +35,7 @@ private:
class MyObject2 { class MyObject2 {
public: public:
MyObject2(int value) : value(value) { MyObject2(int value) : value(value) {
std::cout << toString() << " constructor" << std::endl; print_created(this, toString());
} }
std::string toString() const { std::string toString() const {
...@@ -43,7 +43,7 @@ public: ...@@ -43,7 +43,7 @@ public:
} }
virtual ~MyObject2() { virtual ~MyObject2() {
std::cout << toString() << " destructor" << std::endl; print_destroyed(this);
} }
private: private:
...@@ -54,7 +54,7 @@ private: ...@@ -54,7 +54,7 @@ private:
class MyObject3 : public std::enable_shared_from_this<MyObject3> { class MyObject3 : public std::enable_shared_from_this<MyObject3> {
public: public:
MyObject3(int value) : value(value) { MyObject3(int value) : value(value) {
std::cout << toString() << " constructor" << std::endl; print_created(this, toString());
} }
std::string toString() const { std::string toString() const {
...@@ -62,7 +62,7 @@ public: ...@@ -62,7 +62,7 @@ public:
} }
virtual ~MyObject3() { virtual ~MyObject3() {
std::cout << toString() << " destructor" << std::endl; print_destroyed(this);
} }
private: private:
...@@ -144,4 +144,7 @@ void init_ex_smart_ptr(py::module &m) { ...@@ -144,4 +144,7 @@ void init_ex_smart_ptr(py::module &m) {
m.def("print_myobject3_4", &print_myobject3_4); m.def("print_myobject3_4", &print_myobject3_4);
py::implicitly_convertible<py::int_, MyObject1>(); py::implicitly_convertible<py::int_, MyObject1>();
// Expose constructor stats for the ref type
m.def("cstats_ref", &ConstructorStats::get<ref_tag>);
} }
...@@ -68,3 +68,18 @@ for o in [MyObject3(9), make_myobject3_1(), make_myobject3_2()]: ...@@ -68,3 +68,18 @@ for o in [MyObject3(9), make_myobject3_1(), make_myobject3_2()]:
print_myobject3_2(o) print_myobject3_2(o)
print_myobject3_3(o) print_myobject3_3(o)
print_myobject3_4(o) print_myobject3_4(o)
from example import ConstructorStats, cstats_ref, Object
cstats = [ConstructorStats.get(Object), ConstructorStats.get(MyObject1),
ConstructorStats.get(MyObject2), ConstructorStats.get(MyObject3),
cstats_ref()]
print("Instances not destroyed:", [x.alive() for x in cstats])
o = None
print("Instances not destroyed:", [x.alive() for x in cstats])
print("Object value constructions:", [x.values() for x in cstats])
print("Default constructions:", [x.default_constructions for x in cstats])
print("Copy constructions:", [x.copy_constructions for x in cstats])
#print("Move constructions:", [x.move_constructions >= 0 for x in cstats]) # Doesn't invoke any
print("Copy assignments:", [x.copy_assignments for x in cstats])
print("Move assignments:", [x.move_assignments for x in cstats])
...@@ -8,18 +8,16 @@ ...@@ -8,18 +8,16 @@
*/ */
#include "example.h" #include "example.h"
#include "constructor-stats.h"
#include <pybind11/functional.h> #include <pybind11/functional.h>
/* This is an example class that we'll want to be able to extend from Python */ /* This is an example class that we'll want to be able to extend from Python */
class ExampleVirt { class ExampleVirt {
public: public:
ExampleVirt(int state) : state(state) { ExampleVirt(int state) : state(state) { print_created(this, state); }
cout << "Constructing ExampleVirt.." << endl; ExampleVirt(const ExampleVirt &e) : state(e.state) { print_copy_created(this); }
} ExampleVirt(ExampleVirt &&e) : state(e.state) { print_move_created(this); e.state = 0; }
~ExampleVirt() { print_destroyed(this); }
~ExampleVirt() {
cout << "Destructing ExampleVirt.." << endl;
}
virtual int run(int value) { virtual int run(int value) {
std::cout << "Original implementation of ExampleVirt::run(state=" << state std::cout << "Original implementation of ExampleVirt::run(state=" << state
...@@ -71,8 +69,8 @@ public: ...@@ -71,8 +69,8 @@ public:
class NonCopyable { class NonCopyable {
public: public:
NonCopyable(int a, int b) : value{new int(a*b)} {} NonCopyable(int a, int b) : value{new int(a*b)} { print_created(this, a, b); }
NonCopyable(NonCopyable &&) = default; NonCopyable(NonCopyable &&o) { value = std::move(o.value); print_move_created(this); }
NonCopyable(const NonCopyable &) = delete; NonCopyable(const NonCopyable &) = delete;
NonCopyable() = delete; NonCopyable() = delete;
void operator=(const NonCopyable &) = delete; void operator=(const NonCopyable &) = delete;
...@@ -80,7 +78,7 @@ public: ...@@ -80,7 +78,7 @@ public:
std::string get_value() const { std::string get_value() const {
if (value) return std::to_string(*value); else return "(null)"; if (value) return std::to_string(*value); else return "(null)";
} }
~NonCopyable() { std::cout << "NonCopyable destructor @ " << this << "; value = " << get_value() << std::endl; } ~NonCopyable() { print_destroyed(this); }
private: private:
std::unique_ptr<int> value; std::unique_ptr<int> value;
...@@ -90,11 +88,11 @@ private: ...@@ -90,11 +88,11 @@ private:
// when it is not referenced elsewhere, but copied if it is still referenced. // when it is not referenced elsewhere, but copied if it is still referenced.
class Movable { class Movable {
public: public:
Movable(int a, int b) : value{a+b} {} Movable(int a, int b) : value{a+b} { print_created(this, a, b); }
Movable(const Movable &m) { value = m.value; std::cout << "Movable @ " << this << " copy constructor" << std::endl; } Movable(const Movable &m) { value = m.value; print_copy_created(this); }
Movable(Movable &&m) { value = std::move(m.value); std::cout << "Movable @ " << this << " move constructor" << std::endl; } Movable(Movable &&m) { value = std::move(m.value); print_move_created(this); }
int get_value() const { return value; } int get_value() const { return value; }
~Movable() { std::cout << "Movable destructor @ " << this << "; value = " << get_value() << std::endl; } ~Movable() { print_destroyed(this); }
private: private:
int value; int value;
}; };
...@@ -305,5 +303,6 @@ void init_ex_virtual_functions(py::module &m) { ...@@ -305,5 +303,6 @@ void init_ex_virtual_functions(py::module &m) {
m.def("runExampleVirtBool", &runExampleVirtBool); m.def("runExampleVirtBool", &runExampleVirtBool);
m.def("runExampleVirtVirtual", &runExampleVirtVirtual); m.def("runExampleVirtVirtual", &runExampleVirtVirtual);
m.def("cstats_debug", &ConstructorStats::get<ExampleVirt>);
initialize_inherited_virtuals(m); initialize_inherited_virtuals(m);
} }
...@@ -37,8 +37,6 @@ print(runExampleVirt(ex12p, 20)) ...@@ -37,8 +37,6 @@ print(runExampleVirt(ex12p, 20))
print(runExampleVirtBool(ex12p)) print(runExampleVirtBool(ex12p))
runExampleVirtVirtual(ex12p) runExampleVirtVirtual(ex12p)
sys.stdout.flush()
class VI_AR(A_Repeat): class VI_AR(A_Repeat):
def unlucky_number(self): def unlucky_number(self):
return 99 return 99
...@@ -122,3 +120,16 @@ try: ...@@ -122,3 +120,16 @@ try:
except RuntimeError as e: except RuntimeError as e:
# Don't print the exception message here because it differs under debug/non-debug mode # Don't print the exception message here because it differs under debug/non-debug mode
print("Caught expected exception") print("Caught expected exception")
from example import ConstructorStats
del ex12
del ex12p
del obj
del ncv1
del ncv2
cstats = [ConstructorStats.get(ExampleVirt), ConstructorStats.get(NonCopyable), ConstructorStats.get(Movable)]
print("Instances not destroyed:", [x.alive() for x in cstats])
print("Constructor values:", [x.values() for x in cstats])
print("Copy constructions:", [x.copy_constructions for x in cstats])
print("Move constructions:", [cstats[i].move_constructions >= 1 for i in range(1, len(cstats))])
Constructing ExampleVirt.. ### ExampleVirt @ 0x2073a90 created 10
Original implementation of ExampleVirt::run(state=10, value=20) Original implementation of ExampleVirt::run(state=10, value=20)
30 30
Caught expected exception: Tried to call pure virtual function "ExampleVirt::pure_virtual" Caught expected exception: Tried to call pure virtual function "ExampleVirt::pure_virtual"
Constructing ExampleVirt.. ### ExampleVirt @ 0x2076a00 created 11
ExtendedExampleVirt::run(20), calling parent.. ExtendedExampleVirt::run(20), calling parent..
Original implementation of ExampleVirt::run(state=11, value=21) Original implementation of ExampleVirt::run(state=11, value=21)
32 32
...@@ -78,20 +78,29 @@ VI_DT says: quack quack quack ...@@ -78,20 +78,29 @@ VI_DT says: quack quack quack
Unlucky = 1234 Unlucky = 1234
Lucky = -4.25 Lucky = -4.25
2^2 * 3^2 = 2^2 * 3^2 =
NonCopyable destructor @ 0x1a6c3f0; value = (null) ### NonCopyable @ 0x207df10 created 4 9
### NonCopyable @ 0x7ffcfe866228 created via move constructor
### NonCopyable @ 0x207df10 destroyed
36 36
NonCopyable destructor @ 0x7ffc6d1fbaa8; value = 36 ### NonCopyable @ 0x7ffcfe866228 destroyed
4 + 5 = 4 + 5 =
Movable @ 0x7ffc6d1fbacc copy constructor ### Movable @ 0x207e230 created 4 5
### Movable @ 0x7ffcfe86624c created via copy constructor
9 9
Movable destructor @ 0x7ffc6d1fbacc; value = 9 ### Movable @ 0x7ffcfe86624c destroyed
7 + 7 = 7 + 7 =
Movable @ 0x7ffc6d1fbacc move constructor ### Movable @ 0x20259e0 created 7 7
Movable destructor @ 0x1a6c4d0; value = 14 ### Movable @ 0x7ffcfe86624c created via move constructor
### Movable @ 0x20259e0 destroyed
14 14
Movable destructor @ 0x7ffc6d1fbacc; value = 14 ### Movable @ 0x7ffcfe86624c destroyed
### NonCopyable @ 0x2025a00 created 9 9
Caught expected exception Caught expected exception
NonCopyable destructor @ 0x29a64b0; value = 81 ### ExampleVirt @ 0x2073a90 destroyed
Movable destructor @ 0x1a6c410; value = 9 ### ExampleVirt @ 0x2076a00 destroyed
Destructing ExampleVirt.. ### Movable @ 0x207e230 destroyed
Destructing ExampleVirt.. ### NonCopyable @ 0x2025a00 destroyed
Instances not destroyed: [0, 0, 0]
Constructor values: [['10', '11'], ['4', '9', '9', '9'], ['4', '5', '7', '7']]
Copy constructions: [0, 0, 1]
Move constructions: [True, True]
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
*/ */
#include "example.h" #include "example.h"
#include "constructor-stats.h"
void init_ex_methods_and_attributes(py::module &); void init_ex_methods_and_attributes(py::module &);
void init_ex_python_types(py::module &); void init_ex_python_types(py::module &);
...@@ -34,9 +35,24 @@ void init_issues(py::module &); ...@@ -34,9 +35,24 @@ void init_issues(py::module &);
void init_eigen(py::module &); void init_eigen(py::module &);
#endif #endif
void bind_ConstructorStats(py::module &m) {
py::class_<ConstructorStats>(m, "ConstructorStats")
.def("alive", &ConstructorStats::alive)
.def("values", &ConstructorStats::values)
.def_readwrite("default_constructions", &ConstructorStats::default_constructions)
.def_readwrite("copy_assignments", &ConstructorStats::copy_assignments)
.def_readwrite("move_assignments", &ConstructorStats::move_assignments)
.def_readwrite("copy_constructions", &ConstructorStats::copy_constructions)
.def_readwrite("move_constructions", &ConstructorStats::move_constructions)
.def_static("get", (ConstructorStats &(*)(py::object)) &ConstructorStats::get, py::return_value_policy::reference_internal)
;
}
PYBIND11_PLUGIN(example) { PYBIND11_PLUGIN(example) {
py::module m("example", "pybind example plugin"); py::module m("example", "pybind example plugin");
bind_ConstructorStats(m);
init_ex_methods_and_attributes(m); init_ex_methods_and_attributes(m);
init_ex_python_types(m); init_ex_python_types(m);
init_ex_operator_overloading(m); init_ex_operator_overloading(m);
......
...@@ -8,11 +8,18 @@ ...@@ -8,11 +8,18 @@
*/ */
#include "example.h" #include "example.h"
#include "constructor-stats.h"
#include <pybind11/stl.h> #include <pybind11/stl.h>
#include <pybind11/operators.h> #include <pybind11/operators.h>
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>); PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>);
#define TRACKERS(CLASS) CLASS() { print_default_created(this); } ~CLASS() { print_destroyed(this); }
struct NestABase { int value = -2; TRACKERS(NestABase) };
struct NestA : NestABase { int value = 3; NestA& operator+=(int i) { value += i; return *this; } TRACKERS(NestA) };
struct NestB { NestA a; int value = 4; NestB& operator-=(int i) { value -= i; return *this; } TRACKERS(NestB) };
struct NestC { NestB b; int value = 5; NestC& operator*=(int i) { value *= i; return *this; } TRACKERS(NestC) };
void init_issues(py::module &m) { void init_issues(py::module &m) {
py::module m2 = m.def_submodule("issues"); py::module m2 = m.def_submodule("issues");
...@@ -159,12 +166,6 @@ void init_issues(py::module &m) { ...@@ -159,12 +166,6 @@ void init_issues(py::module &m) {
; ;
// Issue #328: first member in a class can't be used in operators // Issue #328: first member in a class can't be used in operators
#define TRACKERS(CLASS) CLASS() { std::cout << #CLASS "@" << this << " constructor\n"; } \
~CLASS() { std::cout << #CLASS "@" << this << " destructor\n"; }
struct NestABase { int value = -2; TRACKERS(NestABase) };
struct NestA : NestABase { int value = 3; NestA& operator+=(int i) { value += i; return *this; } TRACKERS(NestA) };
struct NestB { NestA a; int value = 4; NestB& operator-=(int i) { value -= i; return *this; } TRACKERS(NestB) };
struct NestC { NestB b; int value = 5; NestC& operator*=(int i) { value *= i; return *this; } TRACKERS(NestC) };
py::class_<NestABase>(m2, "NestABase").def(py::init<>()).def_readwrite("value", &NestABase::value); py::class_<NestABase>(m2, "NestABase").def(py::init<>()).def_readwrite("value", &NestABase::value);
py::class_<NestA>(m2, "NestA").def(py::init<>()).def(py::self += int()) py::class_<NestA>(m2, "NestA").def(py::init<>()).def(py::self += int())
.def("as_base", [](NestA &a) -> NestABase& { return (NestABase&) a; }, py::return_value_policy::reference_internal); .def("as_base", [](NestA &a) -> NestABase& { return (NestABase&) a; }, py::return_value_policy::reference_internal);
......
...@@ -24,15 +24,15 @@ Failed as expected: Incompatible constructor arguments. The following argument t ...@@ -24,15 +24,15 @@ Failed as expected: Incompatible constructor arguments. The following argument t
1. example.issues.StrIssue(arg0: int) 1. example.issues.StrIssue(arg0: int)
2. example.issues.StrIssue() 2. example.issues.StrIssue()
Invoked with: no, such, constructor Invoked with: no, such, constructor
NestABase@0x1152940 constructor ### NestABase @ 0x15eb630 created via default constructor
NestA@0x1152940 constructor ### NestA @ 0x15eb630 created via default constructor
NestABase@0x11f9350 constructor ### NestABase @ 0x1704000 created via default constructor
NestA@0x11f9350 constructor ### NestA @ 0x1704000 created via default constructor
NestB@0x11f9350 constructor ### NestB @ 0x1704000 created via default constructor
NestABase@0x112d0d0 constructor ### NestABase @ 0x1633110 created via default constructor
NestA@0x112d0d0 constructor ### NestA @ 0x1633110 created via default constructor
NestB@0x112d0d0 constructor ### NestB @ 0x1633110 created via default constructor
NestC@0x112d0d0 constructor ### NestC @ 0x1633110 created via default constructor
13 13
103 103
1003 1003
...@@ -43,13 +43,13 @@ NestC@0x112d0d0 constructor ...@@ -43,13 +43,13 @@ NestC@0x112d0d0 constructor
42 42
-2 -2
42 42
NestC@0x112d0d0 destructor ### NestC @ 0x1633110 destroyed
NestB@0x112d0d0 destructor ### NestB @ 0x1633110 destroyed
NestA@0x112d0d0 destructor ### NestA @ 0x1633110 destroyed
NestABase@0x112d0d0 destructor ### NestABase @ 0x1633110 destroyed
42 42
NestA@0x1152940 destructor ### NestA @ 0x15eb630 destroyed
NestABase@0x1152940 destructor ### NestABase @ 0x15eb630 destroyed
NestB@0x11f9350 destructor ### NestB @ 0x1704000 destroyed
NestA@0x11f9350 destructor ### NestA @ 0x1704000 destroyed
NestABase@0x11f9350 destructor ### NestABase @ 0x1704000 destroyed
...@@ -2,15 +2,16 @@ ...@@ -2,15 +2,16 @@
#define __OBJECT_H #define __OBJECT_H
#include <atomic> #include <atomic>
#include "constructor-stats.h"
/// Reference counted object base class /// Reference counted object base class
class Object { class Object {
public: public:
/// Default constructor /// Default constructor
Object() { } Object() { print_default_created(this); }
/// Copy constructor /// Copy constructor
Object(const Object &) : m_refCount(0) {} Object(const Object &) : m_refCount(0) { print_copy_created(this); }
/// Return the current reference count /// Return the current reference count
int getRefCount() const { return m_refCount; }; int getRefCount() const { return m_refCount; };
...@@ -37,11 +38,17 @@ protected: ...@@ -37,11 +38,17 @@ protected:
/** \brief Virtual protected deconstructor. /** \brief Virtual protected deconstructor.
* (Will only be called by \ref ref) * (Will only be called by \ref ref)
*/ */
virtual ~Object() { } virtual ~Object() { print_destroyed(this); }
private: private:
mutable std::atomic<int> m_refCount { 0 }; mutable std::atomic<int> m_refCount { 0 };
}; };
// Tag class used to track constructions of ref objects. When we track constructors, below, we
// track and print out the actual class (e.g. ref<MyObject>), and *also* add a fake tracker for
// ref_tag. This lets us check that the total number of ref<Anything> constructors/destructors is
// correct without having to check each individual ref<Whatever> type individually.
class ref_tag {};
/** /**
* \brief Reference counting helper * \brief Reference counting helper
* *
...@@ -55,37 +62,43 @@ private: ...@@ -55,37 +62,43 @@ private:
template <typename T> class ref { template <typename T> class ref {
public: public:
/// Create a nullptr reference /// Create a nullptr reference
ref() : m_ptr(nullptr) { std::cout << "Created empty ref" << std::endl; } ref() : m_ptr(nullptr) { print_default_created(this); track_default_created((ref_tag*) this); }
/// Construct a reference from a pointer /// Construct a reference from a pointer
ref(T *ptr) : m_ptr(ptr) { ref(T *ptr) : m_ptr(ptr) {
std::cout << "Initialized ref from pointer " << ptr<< std::endl;
if (m_ptr) ((Object *) m_ptr)->incRef(); if (m_ptr) ((Object *) m_ptr)->incRef();
print_created(this, "from pointer", m_ptr); track_created((ref_tag*) this, "from pointer");
} }
/// Copy constructor /// Copy constructor
ref(const ref &r) : m_ptr(r.m_ptr) { ref(const ref &r) : m_ptr(r.m_ptr) {
std::cout << "Initialized ref from ref " << r.m_ptr << std::endl;
if (m_ptr) if (m_ptr)
((Object *) m_ptr)->incRef(); ((Object *) m_ptr)->incRef();
print_copy_created(this, "with pointer", m_ptr); track_copy_created((ref_tag*) this);
} }
/// Move constructor /// Move constructor
ref(ref &&r) : m_ptr(r.m_ptr) { ref(ref &&r) : m_ptr(r.m_ptr) {
std::cout << "Initialized ref with move from ref " << r.m_ptr << std::endl;
r.m_ptr = nullptr; r.m_ptr = nullptr;
print_move_created(this, "with pointer", m_ptr); track_move_created((ref_tag*) this);
} }
/// Destroy this reference /// Destroy this reference
~ref() { ~ref() {
std::cout << "Destructing ref " << m_ptr << std::endl;
if (m_ptr) if (m_ptr)
((Object *) m_ptr)->decRef(); ((Object *) m_ptr)->decRef();
print_destroyed(this); track_destroyed((ref_tag*) this);
} }
/// Move another reference into the current one /// Move another reference into the current one
ref& operator=(ref&& r) { ref& operator=(ref&& r) {
std::cout << "Move-assigning ref " << r.m_ptr << std::endl; print_move_assigned(this, "pointer", r.m_ptr); track_move_assigned((ref_tag*) this);
if (*this == r) if (*this == r)
return *this; return *this;
if (m_ptr) if (m_ptr)
...@@ -97,7 +110,8 @@ public: ...@@ -97,7 +110,8 @@ public:
/// Overwrite this reference with another reference /// Overwrite this reference with another reference
ref& operator=(const ref& r) { ref& operator=(const ref& r) {
std::cout << "Assigning ref " << r.m_ptr << std::endl; print_copy_assigned(this, "pointer", r.m_ptr); track_copy_assigned((ref_tag*) this);
if (m_ptr == r.m_ptr) if (m_ptr == r.m_ptr)
return *this; return *this;
if (m_ptr) if (m_ptr)
...@@ -110,7 +124,8 @@ public: ...@@ -110,7 +124,8 @@ public:
/// Overwrite this reference with a pointer to another object /// Overwrite this reference with a pointer to another object
ref& operator=(T *ptr) { ref& operator=(T *ptr) {
std::cout << "Assigning ptr " << ptr << " to ref" << std::endl; print_values(this, "assigned pointer"); track_values((ref_tag*) this, "assigned pointer");
if (m_ptr == ptr) if (m_ptr == ptr)
return *this; return *this;
if (m_ptr) if (m_ptr)
......
...@@ -9,14 +9,16 @@ remove_long_marker = re.compile(r'([0-9])L') ...@@ -9,14 +9,16 @@ remove_long_marker = re.compile(r'([0-9])L')
remove_hex = re.compile(r'0x[0-9a-fA-F]+') remove_hex = re.compile(r'0x[0-9a-fA-F]+')
shorten_floats = re.compile(r'([1-9][0-9]*\.[0-9]{4})[0-9]*') shorten_floats = re.compile(r'([1-9][0-9]*\.[0-9]{4})[0-9]*')
relaxed = False
def sanitize(lines): def sanitize(lines):
lines = lines.split('\n') lines = lines.split('\n')
for i in range(len(lines)): for i in range(len(lines)):
line = lines[i] line = lines[i]
if line.startswith(" |"): if line.startswith(" |"):
line = "" line = ""
if line.startswith("### "):
# Constructor/destructor output. Useful for example, but unreliable across compilers;
# testing of proper construction/destruction occurs with ConstructorStats mechanism instead
line = ""
line = remove_unicode_marker.sub(r'\1', line) line = remove_unicode_marker.sub(r'\1', line)
line = remove_long_marker.sub(r'\1', line) line = remove_long_marker.sub(r'\1', line)
line = remove_hex.sub(r'0', line) line = remove_hex.sub(r'0', line)
...@@ -28,13 +30,6 @@ def sanitize(lines): ...@@ -28,13 +30,6 @@ def sanitize(lines):
line = line.replace('example.EMode', 'EMode') line = line.replace('example.EMode', 'EMode')
line = line.replace('method of builtins.PyCapsule instance', '') line = line.replace('method of builtins.PyCapsule instance', '')
line = line.strip() line = line.strip()
if relaxed:
lower = line.lower()
# The precise pattern of allocations and deallocations is dependent on the compiler
# and optimization level, so we unfortunately can't reliably check it in this kind of test case
if 'constructor' in lower or 'destructor' in lower \
or 'ref' in lower or 'freeing' in lower:
line = ""
lines[i] = line lines[i] = line
return '\n'.join(sorted([l for l in lines if l != ""])) return '\n'.join(sorted([l for l in lines if l != ""]))
...@@ -44,16 +39,12 @@ if path != '': ...@@ -44,16 +39,12 @@ if path != '':
os.chdir(path) os.chdir(path)
if len(sys.argv) < 2: if len(sys.argv) < 2:
print("Syntax: %s [--relaxed] <test name>" % sys.argv[0]) print("Syntax: %s <test name>" % sys.argv[0])
exit(0) exit(0)
if len(sys.argv) == 3 and sys.argv[1] == '--relaxed':
del sys.argv[1]
relaxed = True
name = sys.argv[1] name = sys.argv[1]
try: try:
output_bytes = subprocess.check_output([sys.executable, name + ".py"], output_bytes = subprocess.check_output([sys.executable, "-u", name + ".py"],
stderr=subprocess.STDOUT) stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
if e.returncode == 99: if e.returncode == 99:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment