μHAL (v2.8.17)
Part of the IPbus software repository
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
constructor_stats.h
Go to the documentation of this file.
1#pragma once
2/*
3 tests/constructor_stats.h -- framework for printing and tracking object
4 instance lifetimes in example/test code.
5
6 Copyright (c) 2016 Jason Rhinelander <jason@imaginary.ca>
7
8 All rights reserved. Use of this source code is governed by a
9 BSD-style license that can be found in the LICENSE file.
10
11This header provides a few useful tools for writing examples or tests that want to check and/or
12display object instance lifetimes. It requires that you include this header and add the following
13function calls to constructors:
14
15 class MyClass {
16 MyClass() { ...; print_default_created(this); }
17 ~MyClass() { ...; print_destroyed(this); }
18 MyClass(const MyClass &c) { ...; print_copy_created(this); }
19 MyClass(MyClass &&c) { ...; print_move_created(this); }
20 MyClass(int a, int b) { ...; print_created(this, a, b); }
21 MyClass &operator=(const MyClass &c) { ...; print_copy_assigned(this); }
22 MyClass &operator=(MyClass &&c) { ...; print_move_assigned(this); }
23
24 ...
25 }
26
27You can find various examples of these in several of the existing testing .cpp files. (Of course
28you don't need to add any of the above constructors/operators that you don't actually have, except
29for the destructor).
30
31Each of these will print an appropriate message such as:
32
33 ### MyClass @ 0x2801910 created via default constructor
34 ### MyClass @ 0x27fa780 created 100 200
35 ### MyClass @ 0x2801910 destroyed
36 ### MyClass @ 0x27fa780 destroyed
37
38You can also include extra arguments (such as the 100, 200 in the output above, coming from the
39value constructor) for all of the above methods which will be included in the output.
40
41For testing, each of these also keeps track the created instances and allows you to check how many
42of the various constructors have been invoked from the Python side via code such as:
43
44 from pybind11_tests import ConstructorStats
45 cstats = ConstructorStats.get(MyClass)
46 print(cstats.alive())
47 print(cstats.default_constructions)
48
49Note that `.alive()` should usually be the first thing you call as it invokes Python's garbage
50collector to actually destroy objects that aren't yet referenced.
51
52For everything except copy and move constructors and destructors, any extra values given to the
53print_...() function is stored in a class-specific values list which you can retrieve and inspect
54from the ConstructorStats instance `.values()` method.
55
56In some cases, when you need to track instances of a C++ class not registered with pybind11, you
57need to add a function returning the ConstructorStats for the C++ class; this can be done with:
58
59 m.def("get_special_cstats", &ConstructorStats::get<SpecialClass>,
60py::return_value_policy::reference)
61
62Finally, you can suppress the output messages, but keep the constructor tracking (for
63inspection/testing in python) by using the functions with `print_` replaced with `track_` (e.g.
64`track_copy_created(this)`).
65
66*/
67
68#include "pybind11_tests.h"
69
70#include <list>
71#include <sstream>
72#include <typeindex>
73#include <unordered_map>
74
75class ConstructorStats {
76protected:
77 std::unordered_map<void *, int> _instances; // Need a map rather than set because members can
78 // shared address with parents
79 std::list<std::string> _values; // Used to track values
80 // (e.g. of value constructors)
81public:
83 int copy_constructions = 0;
84 int move_constructions = 0;
85 int copy_assignments = 0;
86 int move_assignments = 0;
87
88 void copy_created(void *inst) {
89 created(inst);
91 }
92
93 void move_created(void *inst) {
94 created(inst);
96 }
97
98 void default_created(void *inst) {
99 created(inst);
101 }
102
103 void created(void *inst) { ++_instances[inst]; }
104
105 void destroyed(void *inst) {
106 if (--_instances[inst] < 0) {
107 throw std::runtime_error("cstats.destroyed() called with unknown "
108 "instance; potential double-destruction "
109 "or a missing cstats.created()");
110 }
111 }
112
113 static void gc() {
114 // Force garbage collection to ensure any pending destructors are invoked:
115#if defined(PYPY_VERSION)
116 PyObject *globals = PyEval_GetGlobals();
117 PyObject *result = PyRun_String("import gc\n"
118 "for i in range(2):\n"
119 " gc.collect()\n",
120 Py_file_input,
121 globals,
122 globals);
123 if (result == nullptr)
124 throw py::error_already_set();
125 Py_DECREF(result);
126#else
127 py::module_::import("gc").attr("collect")();
128#endif
129 }
130
131 int alive() {
132 gc();
133 int total = 0;
134 for (const auto &p : _instances) {
135 if (p.second > 0) {
136 total += p.second;
137 }
138 }
139 return total;
140 }
141
142 void value() {} // Recursion terminator
143 // Takes one or more values, converts them to strings, then stores them.
144 template <typename T, typename... Tmore>
145 void value(const T &v, Tmore &&...args) {
146 std::ostringstream oss;
147 oss << v;
148 _values.push_back(oss.str());
149 value(std::forward<Tmore>(args)...);
150 }
151
152 // Move out stored values
153 py::list values() {
154 py::list l;
155 for (const auto &v : _values) {
156 l.append(py::cast(v));
157 }
158 _values.clear();
159 return l;
160 }
161
162 // Gets constructor stats from a C++ type index
163 static ConstructorStats &get(std::type_index type) {
164 static std::unordered_map<std::type_index, ConstructorStats> all_cstats;
165 return all_cstats[type];
166 }
167
168 // Gets constructor stats from a C++ type
169 template <typename T>
171#if defined(PYPY_VERSION)
172 gc();
173#endif
174 return get(typeid(T));
175 }
176
177 // Gets constructor stats from a Python class
178 static ConstructorStats &get(py::object class_) {
179 auto &internals = py::detail::get_internals();
180 const std::type_index *t1 = nullptr, *t2 = nullptr;
181 try {
182 auto *type_info
183 = internals.registered_types_py.at((PyTypeObject *) class_.ptr()).at(0);
184 for (auto &p : internals.registered_types_cpp) {
185 if (p.second == type_info) {
186 if (t1) {
187 t2 = &p.first;
188 break;
189 }
190 t1 = &p.first;
191 }
192 }
193 } catch (const std::out_of_range &) {
194 }
195 if (!t1) {
196 throw std::runtime_error("Unknown class passed to ConstructorStats::get()");
197 }
198 auto &cs1 = get(*t1);
199 // If we have both a t1 and t2 match, one is probably the trampoline class; return
200 // whichever has more constructions (typically one or the other will be 0)
201 if (t2) {
202 auto &cs2 = get(*t2);
203 int cs1_total = cs1.default_constructions + cs1.copy_constructions
204 + cs1.move_constructions + (int) cs1._values.size();
205 int cs2_total = cs2.default_constructions + cs2.copy_constructions
206 + cs2.move_constructions + (int) cs2._values.size();
207 if (cs2_total > cs1_total) {
208 return cs2;
209 }
210 }
211 return cs1;
212 }
213};
214
215// To track construction/destruction, you need to call these methods from the various
216// constructors/operators. The ones that take extra values record the given values in the
217// constructor stats values for later inspection.
218template <class T>
219void track_copy_created(T *inst) {
220 ConstructorStats::get<T>().copy_created(inst);
221}
222template <class T>
223void track_move_created(T *inst) {
224 ConstructorStats::get<T>().move_created(inst);
225}
226template <class T, typename... Values>
227void track_copy_assigned(T *, Values &&...values) {
228 auto &cst = ConstructorStats::get<T>();
229 cst.copy_assignments++;
230 cst.value(std::forward<Values>(values)...);
231}
232template <class T, typename... Values>
233void track_move_assigned(T *, Values &&...values) {
234 auto &cst = ConstructorStats::get<T>();
235 cst.move_assignments++;
236 cst.value(std::forward<Values>(values)...);
237}
238template <class T, typename... Values>
239void track_default_created(T *inst, Values &&...values) {
240 auto &cst = ConstructorStats::get<T>();
241 cst.default_created(inst);
242 cst.value(std::forward<Values>(values)...);
243}
244template <class T, typename... Values>
245void track_created(T *inst, Values &&...values) {
246 auto &cst = ConstructorStats::get<T>();
247 cst.created(inst);
248 cst.value(std::forward<Values>(values)...);
249}
250template <class T, typename... Values>
251void track_destroyed(T *inst) {
252 ConstructorStats::get<T>().destroyed(inst);
253}
254template <class T, typename... Values>
255void track_values(T *, Values &&...values) {
256 ConstructorStats::get<T>().value(std::forward<Values>(values)...);
257}
258
260inline const char *format_ptrs(const char *p) { return p; }
261template <typename T>
262py::str format_ptrs(T *p) {
263 return "{:#x}"_s.format(reinterpret_cast<std::uintptr_t>(p));
264}
265template <typename T>
266auto format_ptrs(T &&x) -> decltype(std::forward<T>(x)) {
267 return std::forward<T>(x);
268}
269
270template <class T, typename... Output>
271void print_constr_details(T *inst, const std::string &action, Output &&...output) {
272 py::print("###",
273 py::type_id<T>(),
274 "@",
275 format_ptrs(inst),
276 action,
277 format_ptrs(std::forward<Output>(output))...);
278}
279
280// Verbose versions of the above:
281template <class T, typename... Values>
283 Values &&...values) { // NB: this prints, but doesn't store, given values
284 print_constr_details(inst, "created via copy constructor", values...);
285 track_copy_created(inst);
286}
287template <class T, typename... Values>
289 Values &&...values) { // NB: this prints, but doesn't store, given values
290 print_constr_details(inst, "created via move constructor", values...);
291 track_move_created(inst);
292}
293template <class T, typename... Values>
294void print_copy_assigned(T *inst, Values &&...values) {
295 print_constr_details(inst, "assigned via copy assignment", values...);
296 track_copy_assigned(inst, values...);
297}
298template <class T, typename... Values>
299void print_move_assigned(T *inst, Values &&...values) {
300 print_constr_details(inst, "assigned via move assignment", values...);
301 track_move_assigned(inst, values...);
302}
303template <class T, typename... Values>
304void print_default_created(T *inst, Values &&...values) {
305 print_constr_details(inst, "created via default constructor", values...);
306 track_default_created(inst, values...);
307}
308template <class T, typename... Values>
309void print_created(T *inst, Values &&...values) {
310 print_constr_details(inst, "created", values...);
311 track_created(inst, values...);
312}
313template <class T, typename... Values>
314void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values
315 print_constr_details(inst, "destroyed", values...);
316 track_destroyed(inst);
317}
318template <class T, typename... Values>
319void print_values(T *inst, Values &&...values) {
320 print_constr_details(inst, ":", values...);
321 track_values(inst, values...);
322}
void value(const T &v, Tmore &&...args)
std::list< std::string > _values
void copy_created(void *inst)
void created(void *inst)
void move_created(void *inst)
static ConstructorStats & get()
void destroyed(void *inst)
std::unordered_map< void *, int > _instances
void default_created(void *inst)
static ConstructorStats & get(std::type_index type)
static ConstructorStats & get(py::object class_)
Definition: pytypes.h:1776
Definition: pytypes.h:1167
dict globals()
Return a dictionary representing the global variables in the current execution frame,...
Definition: pybind11.h:1288
void print_default_created(T *inst, Values &&...values)
void track_destroyed(T *inst)
void track_values(T *, Values &&...values)
void track_move_assigned(T *, Values &&...values)
void print_constr_details(T *inst, const std::string &action, Output &&...output)
void print_copy_created(T *inst, Values &&...values)
void print_copy_assigned(T *inst, Values &&...values)
void track_created(T *inst, Values &&...values)
void print_created(T *inst, Values &&...values)
void track_move_created(T *inst)
void print_values(T *inst, Values &&...values)
const char * format_ptrs(const char *p)
Don't cast pointers to Python, print them as strings.
void track_copy_created(T *inst)
void track_copy_assigned(T *, Values &&...values)
void print_destroyed(T *inst, Values &&...values)
void print_move_assigned(T *inst, Values &&...values)
void track_default_created(T *inst, Values &&...values)
void print_move_created(T *inst, Values &&...values)
Internal data structure used to track registered instances and types.
Definition: internals.h:150
type_map< type_info * > registered_types_cpp
Definition: internals.h:152
std::unordered_map< PyTypeObject *, std::vector< type_info * > > registered_types_py
Definition: internals.h:154
Additional type information which does not fit into the PyTypeObject.
Definition: internals.h:196