我们所使用的每个软件产品都包含这样或那样的跟踪功能。
跟踪,英文Trace,又叫做追踪。
软件中的跟踪就是仅仅地跟在执行者的后面进行监视。
当代码超过几千行时,跟踪就显得很重要了。
调试、维护和理解大中型软件的执行流程是很重要的,这是跟踪的基本功能。
在C++中,有许多方法可以进行函数调用跟踪。
其中最简单的方法是在刚进入函数时打印"Entering function X",
仅在返回函数之前进行打印"Leaving function X"
。
但是,这需要进行很多工作,尤其是在函数具有多个return
语句的情况下。
解决上述问题的一种方法是使用跟踪对象。
您创建一个类,该类的构造函数将打印输入消息,存储函数名称,并且其析构函数将打印离开消息。
然后,将该类的实例创建为局部变量。当实例化对象时(调用函数时会发生这种情况),将打印enter消息。
当函数离开时,无论何时,何地或如何发生,对象都会被破坏,并会显示离开消息。
注意,trace对象不应该添加到小的、频繁执行的函数中去。
现在,这样是如何实现的呢?可能是这样简单的事情:
1 struct tracer 2 { 3 std::string name_; // Name of the function 4 5 tracer(std::string const& name) 6 : name_(name) 7 { 8 std::clog << "Entering function " << name_ << std::endl; // Flushing is important 9 } 10 11 ~tracer() 12 { 13 std::clog << "Leaving function " << name_ << std::endl; // Flushing is still important 14 } 15 };
这就是所需要的基础。现在使用它非常简单:
1 void some_class::some_member_function() 2 { 3 tracer _unique_name_{"some_class::some_member_function"); 4 5 // Lots of code and multiple `return` statements... 6 7 }
这是基本用法。它具有一些缺点,因为将始终启用跟踪。例如,发布版本很少需要它,因此只有在_DEBUG
定义宏时才使用它才是一个好的开始。拥有一个特殊的TRACING_ENABLED
宏可能会更好,因此即使在某些有用的发行版中也可以启用它。还可以添加额外的逻辑来检查在运行时设置的标志。
下面一个完整的示例解决方案,在编译时使用预处理器宏来启用和禁用跟踪。
1 #pragma once 2 #ifndef TRACING_H 3 #define TRACING_H 4 5 #include <string> 6 #include <iostream> 7 #include <iomanip> 8 9 // Simple structure to handle function-call tracing. 10 11 // On debug builds, always build with tracing enabled unless explicitly disabled 12 #if defined(_DEBUG) && !defined(TRACING_DISABLED) 13 # define TRACING_ENABLED 14 #endif 15 16 // Define a preprocessor macro to help with the tracing 17 #ifdef TRACING_ENABLED 18 # define TRACE() tracing::tracer _tracer_object__ ## __COUNTER__ {__func__, __FILE__, __LINE__} 19 #else 20 # define TRACE() // Nothing 21 #endif 22 23 #ifdef TRACING_ENABLED 24 namespace tracing 25 { 26 class tracer 27 { 28 public: 29 tracer() = delete; // Disallow default construction 30 tracer(tracer const&) = delete; // Disallow copy construction 31 tracer(tracer&&) = delete; // Disallow move construction 32 tracer& operator=(tracer const&) = delete; // Disallow copy assignment 33 tracer& operator=(tracer&&) = delete; // Disallow move assignment 34 35 tracer(std::string const& fun, std::string const& file, int const line) 36 : function_name{fun}, file_name{file}, line_number{line} 37 { 38 std::clog << "TRACE: Entering function " << function_name << " (" << file_name << ':' << line_number << ')' << std::endl; 39 } 40 41 ~tracer() 42 { 43 std::clog << "TRACE: Leaving function " << function_name << std::endl; 44 } 45 46 private: 47 std::string function_name; 48 std::string file_name; 49 int line_number; 50 }; 51 } 52 #endif // TRACING_ENABLED 53 54 #endif // TRACING_H
使用上述头文件的示例程序:
#include "tracing.h" struct foo { int bar(int value) { TRACE(); if (value < 10) return value * 2; else return value * 3; } }; int main() { TRACE(); foo my_foo; my_foo.bar(20); my_foo.bar(5); }
如上所示,程序的输出可能类似于
输出可能会因所使用的编译器而异。