123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- #pragma once
- /*
- tests/constructor_stats.h -- framework for printing and tracking object
- instance lifetimes in example/test code.
- Copyright (c) 2016 Jason Rhinelander <[email protected]>
- All rights reserved. Use of this source code is governed by a
- BSD-style license that can be found in the LICENSE file.
- This header provides a few useful tools for writing examples or tests that want to check and/or
- display object instance lifetimes. It requires that you include this header and add the following
- function calls to constructors:
- class MyClass {
- MyClass() { ...; print_default_created(this); }
- ~MyClass() { ...; print_destroyed(this); }
- MyClass(const MyClass &c) { ...; print_copy_created(this); }
- MyClass(MyClass &&c) { ...; print_move_created(this); }
- MyClass(int a, int b) { ...; print_created(this, a, b); }
- MyClass &operator=(const MyClass &c) { ...; print_copy_assigned(this); }
- MyClass &operator=(MyClass &&c) { ...; print_move_assigned(this); }
- ...
- }
- You can find various examples of these in several of the existing testing .cpp files. (Of course
- you don't need to add any of the above constructors/operators that you don't actually have, except
- for the destructor).
- Each of these will print an appropriate message such as:
- ### MyClass @ 0x2801910 created via default constructor
- ### MyClass @ 0x27fa780 created 100 200
- ### MyClass @ 0x2801910 destroyed
- ### MyClass @ 0x27fa780 destroyed
- You can also include extra arguments (such as the 100, 200 in the output above, coming from the
- value constructor) for all of the above methods which will be included in the output.
- For testing, each of these also keeps track the created instances and allows you to check how many
- of the various constructors have been invoked from the Python side via code such as:
- from pybind11_tests import ConstructorStats
- cstats = ConstructorStats.get(MyClass)
- print(cstats.alive())
- print(cstats.default_constructions)
- Note that `.alive()` should usually be the first thing you call as it invokes Python's garbage
- collector to actually destroy objects that aren't yet referenced.
- For everything except copy and move constructors and destructors, any extra values given to the
- print_...() function is stored in a class-specific values list which you can retrieve and inspect
- from the ConstructorStats instance `.values()` method.
- In some cases, when you need to track instances of a C++ class not registered with pybind11, you
- need to add a function returning the ConstructorStats for the C++ class; this can be done with:
- m.def("get_special_cstats", &ConstructorStats::get<SpecialClass>, py::return_value_policy::reference)
- Finally, you can suppress the output messages, but keep the constructor tracking (for
- inspection/testing in python) by using the functions with `print_` replaced with `track_` (e.g.
- `track_copy_created(this)`).
- */
- #include "pybind11_tests.h"
- #include <unordered_map>
- #include <list>
- #include <typeindex>
- #include <sstream>
- class ConstructorStats {
- protected:
- std::unordered_map<void*, int> _instances; // Need a map rather than set because members can shared address with parents
- std::list<std::string> _values; // Used to track values (e.g. of value constructors)
- public:
- int default_constructions = 0;
- int copy_constructions = 0;
- int move_constructions = 0;
- int copy_assignments = 0;
- int move_assignments = 0;
- void copy_created(void *inst) {
- created(inst);
- copy_constructions++;
- }
- void move_created(void *inst) {
- created(inst);
- move_constructions++;
- }
- void default_created(void *inst) {
- created(inst);
- default_constructions++;
- }
- void created(void *inst) {
- ++_instances[inst];
- }
- void destroyed(void *inst) {
- if (--_instances[inst] < 0)
- throw std::runtime_error("cstats.destroyed() called with unknown "
- "instance; potential double-destruction "
- "or a missing cstats.created()");
- }
- static void gc() {
- // Force garbage collection to ensure any pending destructors are invoked:
- #if defined(PYPY_VERSION)
- PyObject *globals = PyEval_GetGlobals();
- PyObject *result = PyRun_String(
- "import gc\n"
- "for i in range(2):"
- " gc.collect()\n",
- Py_file_input, globals, globals);
- if (result == nullptr)
- throw py::error_already_set();
- Py_DECREF(result);
- #else
- py::module::import("gc").attr("collect")();
- #endif
- }
- int alive() {
- gc();
- int total = 0;
- for (const auto &p : _instances)
- if (p.second > 0)
- total += p.second;
- return total;
- }
- void value() {} // Recursion terminator
- // Takes one or more values, converts them to strings, then stores them.
- template <typename T, typename... Tmore> void value(const T &v, Tmore &&...args) {
- std::ostringstream oss;
- oss << v;
- _values.push_back(oss.str());
- value(std::forward<Tmore>(args)...);
- }
- // Move out stored values
- py::list values() {
- py::list l;
- for (const auto &v : _values) l.append(py::cast(v));
- _values.clear();
- return l;
- }
- // Gets constructor stats from a C++ type index
- static ConstructorStats& get(std::type_index type) {
- static std::unordered_map<std::type_index, ConstructorStats> all_cstats;
- return all_cstats[type];
- }
- // Gets constructor stats from a C++ type
- template <typename T> static ConstructorStats& get() {
- #if defined(PYPY_VERSION)
- gc();
- #endif
- return get(typeid(T));
- }
- // Gets constructor stats from a Python class
- static ConstructorStats& get(py::object class_) {
- auto &internals = py::detail::get_internals();
- const std::type_index *t1 = nullptr, *t2 = nullptr;
- try {
- auto *type_info = internals.registered_types_py.at((PyTypeObject *) class_.ptr()).at(0);
- for (auto &p : internals.registered_types_cpp) {
- if (p.second == type_info) {
- if (t1) {
- t2 = &p.first;
- break;
- }
- t1 = &p.first;
- }
- }
- }
- catch (std::out_of_range) {}
- if (!t1) throw std::runtime_error("Unknown class passed to ConstructorStats::get()");
- auto &cs1 = get(*t1);
- // If we have both a t1 and t2 match, one is probably the trampoline class; return whichever
- // has more constructions (typically one or the other will be 0)
- if (t2) {
- auto &cs2 = get(*t2);
- int cs1_total = cs1.default_constructions + cs1.copy_constructions + cs1.move_constructions + (int) cs1._values.size();
- int cs2_total = cs2.default_constructions + cs2.copy_constructions + cs2.move_constructions + (int) cs2._values.size();
- if (cs2_total > cs1_total) return cs2;
- }
- return cs1;
- }
- };
- // To track construction/destruction, you need to call these methods from the various
- // constructors/operators. The ones that take extra values record the given values in the
- // constructor stats values for later inspection.
- template <class T> void track_copy_created(T *inst) { ConstructorStats::get<T>().copy_created(inst); }
- template <class T> void track_move_created(T *inst) { ConstructorStats::get<T>().move_created(inst); }
- template <class T, typename... Values> void track_copy_assigned(T *, Values &&...values) {
- auto &cst = ConstructorStats::get<T>();
- cst.copy_assignments++;
- cst.value(std::forward<Values>(values)...);
- }
- template <class T, typename... Values> void track_move_assigned(T *, Values &&...values) {
- auto &cst = ConstructorStats::get<T>();
- cst.move_assignments++;
- cst.value(std::forward<Values>(values)...);
- }
- template <class T, typename... Values> void track_default_created(T *inst, Values &&...values) {
- auto &cst = ConstructorStats::get<T>();
- cst.default_created(inst);
- cst.value(std::forward<Values>(values)...);
- }
- template <class T, typename... Values> void track_created(T *inst, Values &&...values) {
- auto &cst = ConstructorStats::get<T>();
- cst.created(inst);
- cst.value(std::forward<Values>(values)...);
- }
- template <class T, typename... Values> void track_destroyed(T *inst) {
- ConstructorStats::get<T>().destroyed(inst);
- }
- template <class T, typename... Values> void track_values(T *, Values &&...values) {
- ConstructorStats::get<T>().value(std::forward<Values>(values)...);
- }
- /// Don't cast pointers to Python, print them as strings
- inline const char *format_ptrs(const char *p) { return p; }
- template <typename T>
- py::str format_ptrs(T *p) { return "{:#x}"_s.format(reinterpret_cast<std::uintptr_t>(p)); }
- template <typename T>
- auto format_ptrs(T &&x) -> decltype(std::forward<T>(x)) { return std::forward<T>(x); }
- template <class T, typename... Output>
- void print_constr_details(T *inst, const std::string &action, Output &&...output) {
- py::print("###", py::type_id<T>(), "@", format_ptrs(inst), action,
- format_ptrs(std::forward<Output>(output))...);
- }
- // Verbose versions of the above:
- template <class T, typename... Values> void print_copy_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values
- print_constr_details(inst, "created via copy constructor", values...);
- track_copy_created(inst);
- }
- template <class T, typename... Values> void print_move_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values
- print_constr_details(inst, "created via move constructor", values...);
- track_move_created(inst);
- }
- template <class T, typename... Values> void print_copy_assigned(T *inst, Values &&...values) {
- print_constr_details(inst, "assigned via copy assignment", values...);
- track_copy_assigned(inst, values...);
- }
- template <class T, typename... Values> void print_move_assigned(T *inst, Values &&...values) {
- print_constr_details(inst, "assigned via move assignment", values...);
- track_move_assigned(inst, values...);
- }
- template <class T, typename... Values> void print_default_created(T *inst, Values &&...values) {
- print_constr_details(inst, "created via default constructor", values...);
- track_default_created(inst, values...);
- }
- template <class T, typename... Values> void print_created(T *inst, Values &&...values) {
- print_constr_details(inst, "created", values...);
- track_created(inst, values...);
- }
- template <class T, typename... Values> void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values
- print_constr_details(inst, "destroyed", values...);
- track_destroyed(inst);
- }
- template <class T, typename... Values> void print_values(T *inst, Values &&...values) {
- print_constr_details(inst, ":", values...);
- track_values(inst, values...);
- }
|