我們所使用的每個軟件產品都包含這樣或那樣的跟蹤功能。
跟蹤,英文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); }
如上所示,程序的輸出可能類似於
輸出可能會因所使用的編譯器而異。