You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							249 lines
						
					
					
						
							10 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	
							249 lines
						
					
					
						
							10 KiB
						
					
					
				| #pragma once | |
| /* | |
|     tests/constructor_stats.h -- framework for printing and tracking object | |
|     instance lifetimes in example/test code. | |
|  | |
|     Copyright (c) 2016 Jason Rhinelander <jason@imaginary.ca> | |
|  | |
|     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 example .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 example 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()"); | |
|     } | |
| 
 | |
|     int alive() { | |
|         // Force garbage collection to ensure any pending destructors are invoked: | |
|         py::module::import("gc").attr("collect")(); | |
|         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() { | |
|         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(class_.ptr()); | |
|             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...); | |
| } | |
| 
 |