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