如何跟踪通话统计?C++

How to keep track of call statistics? C++

本文关键字:统计 C++ 何跟踪 跟踪      更新时间:2023-10-16

我正在做一个向用户提供统计数据的项目。我创建了一个名为 Dog 的类,它有几个功能。说话,哇,跑,取等。

我想要一个函数来吐出每个函数被调用了多少次。我也对构造函数调用和析构函数调用感兴趣。

我有一个定义所有函数的头文件,然后是一个单独的 .cc 文件来实现它们。我的问题是,有没有办法跟踪每个函数被调用的次数?

我有一个名为 print 的函数,它将获取"统计信息",然后将它们输出到标准输出。我正在考虑使用静态整数作为类本身的一部分,声明几个整数来跟踪这些事情。我知道编译器将创建整数的副本并将其初始化为最小值,然后我将递增 .cc 函数中的整数。

我还考虑过将静态整数作为 .cc 中的全局变量。哪种方式更容易?或者有更好的方法可以做到这一点吗?

任何帮助将不胜感激!

使用静态成员变量是要走的路。但是,编译器不会"创建整数的副本并将其初始化为最小值";您必须在 .cc 文件中为每个定义提供一个定义,并在那里将其初始化为 0。(如果您使用的是 C++11,情况会有所不同,但基本思想是相同的。

没有理由使用静态全局变量而不是静态成员。

傅炯:

class Foo {
  static int countCtor_;
  static int countDtor_;
  static int countprint_:
  Foo();
  ~Foo();
  static void print();
};

foo.cc:

#include <iostream>
#include "foo.h"
int Foo::countCtor_ = 0;
int Foo::countDtor_ = 0;
int Foo::countprint_ = 0;
Foo::Foo() {
  ++countCtor_;
  // Something here
}
Foo::~Foo() {
  ++countDtor_;
  // Something here
}
void Foo::print() {
  ++countprint_;
  std::cout << "Ctor:  " << countCtor_ << "n"
            << "Dtor:  " << countDtor_ << "n"
            << "print: " << countprint_ << "n";
}

但是如果你有很多函数,所涉及的重复有点烦人 - 当你的意思是++countBaz_时countBar_,很容易不小心做++所以你可能想要一些更花哨的东西,比如静态映射和一个递增计数的宏[__FUNC__],所以你可以在每个函数中使用完全相同的行。喜欢这个:

傅炯:

#include <map>
class Foo {
  static std::map<const char*, int> counts_;
  Foo();
  ~Foo();
  void print();
};

foo.cc:

#include <iostream>
#include "foo.h"
std::map<const char *, int> Foo::counts_;
#define INC_COUNT_() do { ++counts_[__FUNC__]; } while (0)
Foo::Foo() {
  INC_COUNT_();
  // Something here
}
Foo::~Foo() {
  INC_COUNT_();
  // Something here
}
void Foo::print() {
  INC_COUNT_();
  for (std::map<const char *, int>::const_iterator it = counts_.begin(); 
       it != counts_.end(); ++it) {
    std::cout << it->first << ": " << it->second << "n";
  }
}

在上面的示例代码中,__FUNC__是一个占位符。遗憾的是,没有符合标准的值可以在其位置使用。大多数编译器都有一些__func__、__FUNC__、__FUNCTION__、__FUNCSIG__和__PRETTY_FUNCTION__子集。但是,这些都不是 C++03 中的标准配置。C++11 确实标准化了__func__,但仅作为"实现定义的字符串",这不能保证有用,甚至不唯一。最重要的是,不同编译器上的值会有所不同。此外,其中一些可能是宏而不是标识符,以使事情更有趣。

如果你想要真正可移植的代码,在 C++11 中,你可以使用 string(__func__) + ":" + STRINGIZE(__LINE__) 这样的东西——这会有点难看,但至少每个函数都有一个唯一的名称。而在 C++03 中,没有等价物。如果您只需要"足够便携",请查阅您使用的每个编译器的文档,或者依赖 autoconf 之类的东西。

有什么理由不能使用标准分析工具来计算这些调用吗?像 gprof 这样的东西?

否则,静态整数将是要走的路。

假设您希望在程序中始终跟踪这些统计信息,则可以使用函数名称的unordered_map

std::unordered_map<const char *, unsigned> stats;
void foo () {
    // use __FUNCDNAME__ for MSVC
    ++stats[__PRETTY_FUNCTION__];
    //...
}

使用编译器特定的函数名称说明符是故意获取修饰的函数名称。这样,重载的函数名称就会计为单独的函数。

这种技术允许您轻松添加新函数而无需考虑其他任何事情,但如果存在哈希冲突,则需要支付少量额外费用(可以通过调整stats映射的大小来弥补)。字符串上没有计算哈希,因为键是指针类型,它只是使用指针值本身作为哈希。

如果这只是用于分析的一次性代码,则应首先尝试使用平台上可用的代码分析工具。

您可以将static局部变量放入方法本身中,这似乎更干净,因为这些变量在逻辑上没有连接到类,因此没有理由使它们成为成员。

此外,您可以使用宏来简化工作。我通常不建议使用宏,但这似乎是一个合适的用法:

#define DEFINE_COUNTER 
   static int noCalls = 0; 
   noCalls++;

void foo()
{
   DEFINE_COUNTER
}

使用实现观察者模式或方法调用拦截的库。 您可以从此列表中选择一种,或使用维生素之类的东西。