1

For a given class, one needs sometimes to trace calls to constructors/destructor.

An obvious way is to put some traces in these methods but it is often tedious and intrusive for the class to be instrumented. A possible way would be to inherit from some TraceConstructor struct:

#include <iostream> template<auto& OUTPUT> struct TraceConstructor { TraceConstructor () { OUTPUT << "default constructor\n"; }; ~TraceConstructor () { OUTPUT << "destructor\n"; }; TraceConstructor ([[maybe_unused]] TraceConstructor const& o) { OUTPUT << "copy constructor\n"; }; TraceConstructor ([[maybe_unused]] TraceConstructor && o) { OUTPUT << "move constructor\n"; }; }; struct A : TraceConstructor<std::cout> { int n_; A(int n) : n_(n) {} }; int main() { A a1 {4}; A a2 = a1; A a3 = std::move(a1); } 

Question: Instead of reinventing the wheel, is there some idiomatic feature like this (in std, boost, other...) and that is more complete than this naive (and probably incomplete) approach ?

Note: I just focus here on the constructors/destructor here but obviously one could consider assignment operator as well.

7
  • What's wrong with providing a base class that supplies constructors and destructor that you want to trace? The base class will need to provide a default constructor, copy and move constructors, [and assignment operators] and destructor as a (likely) minimum - any other constructors and assignment operator would be specific to requirements of your derived class (which would need to explicitly call the base class functions anyway). Commented Nov 3, 2024 at 10:24
  • This kind of classes seems really temporary for debugging, specific (logging). I doubt there is a library which does what you want out of the box; and asking for libraries is a close reason... Commented Nov 3, 2024 at 11:17
  • inheritance is intrusive too (but indeed easier to remove afterward). Your given class seems to do the job you expect. Commented Nov 3, 2024 at 11:20
  • You might be able to do this with a debugger as well, e.g.: stackoverflow.com/q/53445637 Commented Nov 3, 2024 at 11:38
  • 3
    Cobbling together a class like your TraceConstructor and inheriting from it just as you've done is the idiomatic way I'd do it. Commented Nov 3, 2024 at 12:00

2 Answers 2

2

You have two options: Define special member tracer as CRTP:

template<typename crtp_derived> struct special_pre_tracer { /*...*/ }; class A : public special_pre_tracer<A> { /*...*/ }; 

Or do a mixin:

template<typename base_t> struct special_post_tracer : base_t { template<typename... t_va> requires std::constructible_from<base_t, t_va&&...> special_post_tracer(t_va&&... obj_va) : base_t(std::forward<t_va>(obj_va)...) {/*...*/}; /*...*/ }; class A /*...*/; special_post_tracer<A> a_obj; 

The former method can only trace the 5 special functions, under specific conditions, while the later can trace all constructors. But the second method does its prompt after conclusion of each function, while the first method prints before doing anything. Now for function local reflection you can use C++20 <source_location> or C++23 <stacktrace>:

#include <source_location> //C++23: #include <print> #include <cstdio> void f() { auto my = std::source_location::current(); std::println(stdout, "{:s}", my.function_name()); }; 

I skip illustration on <stacktrace>. The above pattern can be implemented inside any desired function from our special tracer class. One last note is to create a key for enabling debugging:

constexpr static bool enable_special_tracing = true; template<typename> struct noop{}; template<typename T> using my_special_pre_tracer = std::conditional_t<enable_special_tracing, special_pre_tracer<T>, noop<T>>; template<typename T> using my_special_post_tracer = std::conditional_t<enable_special_tracing, special_post_tracer<T>, T>; class A : my_special_pre_tracer<A> {/**/}; my_special_post_tracer<A> a_obj; 

For the sake of succinctness, I used both methods in the last snippet; either one can be omitted, and the behavior of the program is control via enable_special_tracing.

Sign up to request clarification or add additional context in comments.

Comments

1

You can use the __PRETTY_PRINT__ macro for this. For example:

#define PRETTY(o) { o << __PRETTY_FUNCTION__ << '\n'; } template<auto& OUTPUT> struct TraceConstructor { TraceConstructor () PRETTY(OUTPUT) ~TraceConstructor () PRETTY(OUTPUT) TraceConstructor ([[maybe_unused]] TraceConstructor const& o) PRETTY(OUTPUT) TraceConstructor ([[maybe_unused]] TraceConstructor && o) PRETTY(OUTPUT) }; 

Output:

TraceConstructor<OUTPUT>::TraceConstructor() [with auto& OUTPUT = std::cout] TraceConstructor<OUTPUT>::TraceConstructor(const TraceConstructor<OUTPUT>&) [with auto& OUTPUT = std::cout] TraceConstructor<OUTPUT>::TraceConstructor(TraceConstructor<OUTPUT>&&) [with auto& OUTPUT = std::cout] TraceConstructor<OUTPUT>::~TraceConstructor() [with auto& OUTPUT = std::cout] TraceConstructor<OUTPUT>::~TraceConstructor() [with auto& OUTPUT = std::cout] TraceConstructor<OUTPUT>::~TraceConstructor() [with auto& OUTPUT = std::cout] 

3 Comments

Nice answer, but it's neither portable nor idiomatic of C++ which is what the OP is asking.
std::source_location would avoid the MACRO
@Jarod42 yeah I've been away for a long time. C++ has changed a lot

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.