constructor_stats.h revision 11986
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 example .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 example 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>, py::return_value_policy::reference) 60 61Finally, you can suppress the output messages, but keep the constructor tracking (for 62inspection/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 73class ConstructorStats { 74protected: 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) 77public: 78 int default_constructions = 0; 79 int copy_constructions = 0; 80 int move_constructions = 0; 81 int copy_assignments = 0; 82 int move_assignments = 0; 83 84 void copy_created(void *inst) { 85 created(inst); 86 copy_constructions++; 87 } 88 void move_created(void *inst) { 89 created(inst); 90 move_constructions++; 91 } 92 void default_created(void *inst) { 93 created(inst); 94 default_constructions++; 95 } 96 void created(void *inst) { 97 ++_instances[inst]; 98 }; 99 void destroyed(void *inst) { 100 if (--_instances[inst] < 0) 101 throw std::runtime_error("cstats.destroyed() called with unknown instance; potential double-destruction or a missing cstats.created()"); 102 } 103 104 int alive() { 105 // Force garbage collection to ensure any pending destructors are invoked: 106 py::module::import("gc").attr("collect")(); 107 int total = 0; 108 for (const auto &p : _instances) if (p.second > 0) total += p.second; 109 return total; 110 } 111 112 void value() {} // Recursion terminator 113 // Takes one or more values, converts them to strings, then stores them. 114 template <typename T, typename... Tmore> void value(const T &v, Tmore &&...args) { 115 std::ostringstream oss; 116 oss << v; 117 _values.push_back(oss.str()); 118 value(std::forward<Tmore>(args)...); 119 } 120 121 // Move out stored values 122 py::list values() { 123 py::list l; 124 for (const auto &v : _values) l.append(py::cast(v)); 125 _values.clear(); 126 return l; 127 } 128 129 // Gets constructor stats from a C++ type index 130 static ConstructorStats& get(std::type_index type) { 131 static std::unordered_map<std::type_index, ConstructorStats> all_cstats; 132 return all_cstats[type]; 133 } 134 135 // Gets constructor stats from a C++ type 136 template <typename T> static ConstructorStats& get() { 137 return get(typeid(T)); 138 } 139 140 // Gets constructor stats from a Python class 141 static ConstructorStats& get(py::object class_) { 142 auto &internals = py::detail::get_internals(); 143 const std::type_index *t1 = nullptr, *t2 = nullptr; 144 try { 145 auto *type_info = internals.registered_types_py.at(class_.ptr()); 146 for (auto &p : internals.registered_types_cpp) { 147 if (p.second == type_info) { 148 if (t1) { 149 t2 = &p.first; 150 break; 151 } 152 t1 = &p.first; 153 } 154 } 155 } 156 catch (std::out_of_range) {} 157 if (!t1) throw std::runtime_error("Unknown class passed to ConstructorStats::get()"); 158 auto &cs1 = get(*t1); 159 // If we have both a t1 and t2 match, one is probably the trampoline class; return whichever 160 // has more constructions (typically one or the other will be 0) 161 if (t2) { 162 auto &cs2 = get(*t2); 163 int cs1_total = cs1.default_constructions + cs1.copy_constructions + cs1.move_constructions + (int) cs1._values.size(); 164 int cs2_total = cs2.default_constructions + cs2.copy_constructions + cs2.move_constructions + (int) cs2._values.size(); 165 if (cs2_total > cs1_total) return cs2; 166 } 167 return cs1; 168 } 169}; 170 171// To track construction/destruction, you need to call these methods from the various 172// constructors/operators. The ones that take extra values record the given values in the 173// constructor stats values for later inspection. 174template <class T> void track_copy_created(T *inst) { ConstructorStats::get<T>().copy_created(inst); } 175template <class T> void track_move_created(T *inst) { ConstructorStats::get<T>().move_created(inst); } 176template <class T, typename... Values> void track_copy_assigned(T *, Values &&...values) { 177 auto &cst = ConstructorStats::get<T>(); 178 cst.copy_assignments++; 179 cst.value(std::forward<Values>(values)...); 180} 181template <class T, typename... Values> void track_move_assigned(T *, Values &&...values) { 182 auto &cst = ConstructorStats::get<T>(); 183 cst.move_assignments++; 184 cst.value(std::forward<Values>(values)...); 185} 186template <class T, typename... Values> void track_default_created(T *inst, Values &&...values) { 187 auto &cst = ConstructorStats::get<T>(); 188 cst.default_created(inst); 189 cst.value(std::forward<Values>(values)...); 190} 191template <class T, typename... Values> void track_created(T *inst, Values &&...values) { 192 auto &cst = ConstructorStats::get<T>(); 193 cst.created(inst); 194 cst.value(std::forward<Values>(values)...); 195} 196template <class T, typename... Values> void track_destroyed(T *inst) { 197 ConstructorStats::get<T>().destroyed(inst); 198} 199template <class T, typename... Values> void track_values(T *, Values &&...values) { 200 ConstructorStats::get<T>().value(std::forward<Values>(values)...); 201} 202 203/// Don't cast pointers to Python, print them as strings 204inline const char *format_ptrs(const char *p) { return p; } 205template <typename T> 206py::str format_ptrs(T *p) { return "{:#x}"_s.format(reinterpret_cast<std::uintptr_t>(p)); } 207template <typename T> 208auto format_ptrs(T &&x) -> decltype(std::forward<T>(x)) { return std::forward<T>(x); } 209 210template <class T, typename... Output> 211void print_constr_details(T *inst, const std::string &action, Output &&...output) { 212 py::print("###", py::type_id<T>(), "@", format_ptrs(inst), action, 213 format_ptrs(std::forward<Output>(output))...); 214} 215 216// Verbose versions of the above: 217template <class T, typename... Values> void print_copy_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values 218 print_constr_details(inst, "created via copy constructor", values...); 219 track_copy_created(inst); 220} 221template <class T, typename... Values> void print_move_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values 222 print_constr_details(inst, "created via move constructor", values...); 223 track_move_created(inst); 224} 225template <class T, typename... Values> void print_copy_assigned(T *inst, Values &&...values) { 226 print_constr_details(inst, "assigned via copy assignment", values...); 227 track_copy_assigned(inst, values...); 228} 229template <class T, typename... Values> void print_move_assigned(T *inst, Values &&...values) { 230 print_constr_details(inst, "assigned via move assignment", values...); 231 track_move_assigned(inst, values...); 232} 233template <class T, typename... Values> void print_default_created(T *inst, Values &&...values) { 234 print_constr_details(inst, "created via default constructor", values...); 235 track_default_created(inst, values...); 236} 237template <class T, typename... Values> void print_created(T *inst, Values &&...values) { 238 print_constr_details(inst, "created", values...); 239 track_created(inst, values...); 240} 241template <class T, typename... Values> void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values 242 print_constr_details(inst, "destroyed", values...); 243 track_destroyed(inst); 244} 245template <class T, typename... Values> void print_values(T *inst, Values &&...values) { 246 print_constr_details(inst, ":", values...); 247 track_values(inst, values...); 248} 249 250