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 
11 This header provides a few useful tools for writing examples or tests that want to check and/or
12 display object instance lifetimes. It requires that you include this header and add the following
13 function 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 
27 You can find various examples of these in several of the existing testing .cpp files. (Of course
28 you don't need to add any of the above constructors/operators that you don't actually have, except
29 for the destructor).
30 
31 Each 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 
38 You can also include extra arguments (such as the 100, 200 in the output above, coming from the
39 value constructor) for all of the above methods which will be included in the output.
40 
41 For testing, each of these also keeps track the created instances and allows you to check how many
42 of 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 
49 Note that `.alive()` should usually be the first thing you call as it invokes Python's garbage
50 collector to actually destroy objects that aren't yet referenced.
51 
52 For everything except copy and move constructors and destructors, any extra values given to the
53 print_...() function is stored in a class-specific values list which you can retrieve and inspect
54 from the ConstructorStats instance `.values()` method.
55 
56 In some cases, when you need to track instances of a C++ class not registered with pybind11, you
57 need 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>, py::return_value_policy::reference)
60 
61 Finally, you can suppress the output messages, but keep the constructor tracking (for
62 inspection/testing in python) by using the functions with `print_` replaced with `track_` (e.g.
63 `track_copy_created(this)`).
64 
65 */
66 
67 #include "pybind11_tests.h"
68 #include <unordered_map>
69 #include <list>
70 #include <typeindex>
71 #include <sstream>
72 
73 class ConstructorStats {
74 protected:
75  std::unordered_map<void*, int> _instances; // Need a map rather than set because members can shared address with parents
76  std::list<std::string> _values; // Used to track values (e.g. of value constructors)
77 public:
83 
84  void copy_created(void *inst) {
85  created(inst);
86  copy_constructions++;
87  }
88 
89  void move_created(void *inst) {
90  created(inst);
91  move_constructions++;
92  }
93 
94  void default_created(void *inst) {
95  created(inst);
96  default_constructions++;
97  }
98 
99  void created(void *inst) {
100  ++_instances[inst];
101  }
102 
103  void destroyed(void *inst) {
104  if (--_instances[inst] < 0)
105  throw std::runtime_error("cstats.destroyed() called with unknown "
106  "instance; potential double-destruction "
107  "or a missing cstats.created()");
108  }
109 
110  static void gc() {
111  // Force garbage collection to ensure any pending destructors are invoked:
112 #if defined(PYPY_VERSION)
113  PyObject *globals = PyEval_GetGlobals();
114  PyObject *result = PyRun_String(
115  "import gc\n"
116  "for i in range(2):"
117  " gc.collect()\n",
118  Py_file_input, globals, globals);
119  if (result == nullptr)
120  throw py::error_already_set();
121  Py_DECREF(result);
122 #else
123  py::module::import("gc").attr("collect")();
124 #endif
125  }
126 
127  int alive() {
128  gc();
129  int total = 0;
130  for (const auto &p : _instances)
131  if (p.second > 0)
132  total += p.second;
133  return total;
134  }
135 
136  void value() {} // Recursion terminator
137  // Takes one or more values, converts them to strings, then stores them.
138  template <typename T, typename... Tmore> void value(const T &v, Tmore &&...args) {
139  std::ostringstream oss;
140  oss << v;
141  _values.push_back(oss.str());
142  value(std::forward<Tmore>(args)...);
143  }
144 
145  // Move out stored values
146  py::list values() {
147  py::list l;
148  for (const auto &v : _values) l.append(py::cast(v));
149  _values.clear();
150  return l;
151  }
152 
153  // Gets constructor stats from a C++ type index
154  static ConstructorStats& get(std::type_index type) {
155  static std::unordered_map<std::type_index, ConstructorStats> all_cstats;
156  return all_cstats[type];
157  }
158 
159  // Gets constructor stats from a C++ type
160  template <typename T> static ConstructorStats& get() {
161 #if defined(PYPY_VERSION)
162  gc();
163 #endif
164  return get(typeid(T));
165  }
166 
167  // Gets constructor stats from a Python class
168  static ConstructorStats& get(py::object class_) {
170  const std::type_index *t1 = nullptr, *t2 = nullptr;
171  try {
172  auto *type_info = internals.registered_types_py.at((PyTypeObject *) class_.ptr()).at(0);
173  for (auto &p : internals.registered_types_cpp) {
174  if (p.second == type_info) {
175  if (t1) {
176  t2 = &p.first;
177  break;
178  }
179  t1 = &p.first;
180  }
181  }
182  }
183  catch (const std::out_of_range&) {}
184  if (!t1) throw std::runtime_error("Unknown class passed to ConstructorStats::get()");
185  auto &cs1 = get(*t1);
186  // If we have both a t1 and t2 match, one is probably the trampoline class; return whichever
187  // has more constructions (typically one or the other will be 0)
188  if (t2) {
189  auto &cs2 = get(*t2);
190  int cs1_total = cs1.default_constructions + cs1.copy_constructions + cs1.move_constructions + (int) cs1._values.size();
191  int cs2_total = cs2.default_constructions + cs2.copy_constructions + cs2.move_constructions + (int) cs2._values.size();
192  if (cs2_total > cs1_total) return cs2;
193  }
194  return cs1;
195  }
196 };
197 
198 // To track construction/destruction, you need to call these methods from the various
199 // constructors/operators. The ones that take extra values record the given values in the
200 // constructor stats values for later inspection.
201 template <class T> void track_copy_created(T *inst) { ConstructorStats::get<T>().copy_created(inst); }
202 template <class T> void track_move_created(T *inst) { ConstructorStats::get<T>().move_created(inst); }
203 template <class T, typename... Values> void track_copy_assigned(T *, Values &&...values) {
204  auto &cst = ConstructorStats::get<T>();
205  cst.copy_assignments++;
206  cst.value(std::forward<Values>(values)...);
207 }
208 template <class T, typename... Values> void track_move_assigned(T *, Values &&...values) {
209  auto &cst = ConstructorStats::get<T>();
210  cst.move_assignments++;
211  cst.value(std::forward<Values>(values)...);
212 }
213 template <class T, typename... Values> void track_default_created(T *inst, Values &&...values) {
214  auto &cst = ConstructorStats::get<T>();
215  cst.default_created(inst);
216  cst.value(std::forward<Values>(values)...);
217 }
218 template <class T, typename... Values> void track_created(T *inst, Values &&...values) {
219  auto &cst = ConstructorStats::get<T>();
220  cst.created(inst);
221  cst.value(std::forward<Values>(values)...);
222 }
223 template <class T, typename... Values> void track_destroyed(T *inst) {
224  ConstructorStats::get<T>().destroyed(inst);
225 }
226 template <class T, typename... Values> void track_values(T *, Values &&...values) {
227  ConstructorStats::get<T>().value(std::forward<Values>(values)...);
228 }
229 
231 inline const char *format_ptrs(const char *p) { return p; }
232 template <typename T>
233 py::str format_ptrs(T *p) { return "{:#x}"_s.format(reinterpret_cast<std::uintptr_t>(p)); }
234 template <typename T>
235 auto format_ptrs(T &&x) -> decltype(std::forward<T>(x)) { return std::forward<T>(x); }
236 
237 template <class T, typename... Output>
238 void print_constr_details(T *inst, const std::string &action, Output &&...output) {
239  py::print("###", py::type_id<T>(), "@", format_ptrs(inst), action,
240  format_ptrs(std::forward<Output>(output))...);
241 }
242 
243 // Verbose versions of the above:
244 template <class T, typename... Values> void print_copy_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values
245  print_constr_details(inst, "created via copy constructor", values...);
246  track_copy_created(inst);
247 }
248 template <class T, typename... Values> void print_move_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values
249  print_constr_details(inst, "created via move constructor", values...);
250  track_move_created(inst);
251 }
252 template <class T, typename... Values> void print_copy_assigned(T *inst, Values &&...values) {
253  print_constr_details(inst, "assigned via copy assignment", values...);
254  track_copy_assigned(inst, values...);
255 }
256 template <class T, typename... Values> void print_move_assigned(T *inst, Values &&...values) {
257  print_constr_details(inst, "assigned via move assignment", values...);
258  track_move_assigned(inst, values...);
259 }
260 template <class T, typename... Values> void print_default_created(T *inst, Values &&...values) {
261  print_constr_details(inst, "created via default constructor", values...);
262  track_default_created(inst, values...);
263 }
264 template <class T, typename... Values> void print_created(T *inst, Values &&...values) {
265  print_constr_details(inst, "created", values...);
266  track_created(inst, values...);
267 }
268 template <class T, typename... Values> void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values
269  print_constr_details(inst, "destroyed", values...);
270  track_destroyed(inst);
271 }
272 template <class T, typename... Values> void print_values(T *inst, Values &&...values) {
273  print_constr_details(inst, ":", values...);
274  track_values(inst, values...);
275 }
void print(const Matrix &A, const string &s, ostream &stream)
Definition: Matrix.cpp:155
void created(void *inst)
void value(const T &v, Tmore &&...args)
return int(ret)+1
void track_copy_assigned(T *, Values &&...values)
ArrayXcf v
Definition: Cwise_arg.cpp:1
Definition: pytypes.h:1322
std::unordered_map< void *, int > _instances
PYBIND11_NOINLINE internals & get_internals()
Return a reference to the current internals data.
Definition: internals.h:245
type_map< type_info * > registered_types_cpp
Definition: internals.h:97
void print_destroyed(T *inst, Values &&...values)
void print_copy_assigned(T *inst, Values &&...values)
const char * format_ptrs(const char *p)
Don&#39;t cast pointers to Python, print them as strings.
void print_copy_created(T *inst, Values &&...values)
void move_created(void *inst)
void default_created(void *inst)
void track_move_created(T *inst)
static const Line3 l(Rot3(), 1, 1)
void print_default_created(T *inst, Values &&...values)
std::list< std::string > _values
void print_values(T *inst, Values &&...values)
Values result
void print_move_assigned(T *inst, Values &&...values)
void copy_created(void *inst)
EIGEN_DEVICE_FUNC NewType cast(const OldType &x)
Eigen::Triplet< double > T
void track_copy_created(T *inst)
void track_destroyed(T *inst)
void track_move_assigned(T *, Values &&...values)
std::vector< float > Values
void print_created(T *inst, Values &&...values)
void print_constr_details(T *inst, const std::string &action, Output &&...output)
float * p
void destroyed(void *inst)
void print_move_created(T *inst, Values &&...values)
std::unordered_map< PyTypeObject *, std::vector< type_info * > > registered_types_py
Definition: internals.h:98
set noclip points set clip one set noclip two set bar set border lt lw set xdata set ydata set zdata set x2data set y2data set boxwidth set dummy x
void track_created(T *inst, Values &&...values)
void track_default_created(T *inst, Values &&...values)
dict globals()
Definition: pybind11.h:948
void track_values(T *, Values &&...values)


gtsam
Author(s):
autogenerated on Sat May 8 2021 02:41:51