【问题标题】:Simplest way to count instances of an object计算对象实例的最简单方法
【发布时间】:2011-08-17 18:32:26
【问题描述】:

我想知道在某个执行点分配的某些对象的确切实例数。主要是为了寻找可能的内存泄漏(我主要使用 RAII,几乎没有新的,但我仍然可以在添加新元素或类似的东西之前忘记向量上的 .clear())。 Ofc 我可以有一个

atomic<int> cntMyObject;

我 -- 在析构函数中,++ 在构造函数中增加,cpy 构造函数(我希望我涵盖了所有内容:))。 但这是每个班级的硬编码。在“发布”模式下禁用它并不简单。 那么有没有什么简单优雅的方法可以轻松禁用来统计对象实例呢?

【问题讨论】:

  • 为什么不使用配置文件来查找内存泄漏?...
  • 不是赋值运算符——它不会改变该类型现有对象的数量,只是改变其中一个对象的值。
  • 尽管我发现添加全局对象计数器的想法很有趣,但我想说的是,对于消除内存错误的实际问题,通过 Valgrind 运行程序将是一个更平易近人的解决方案,还有更有意义的信息。
  • 如果你认为像谷歌 HEAPPProfiler 这样的东西很好,但是一旦我搞砸了 STL 的东西(非线程安全更新)并且它没有检测到泄漏(它确实帮助我找到了它们,因为我注意到了圆圈重新加载相同的东西变得越来越大)。另外我不知道如何专注于 HeapProfiler 中的某些类,我只知道生成“整个程序内存使用”。
  • 您准备好接受编译器特定的解决方案了吗?我可以提供一个非侵入式 gcc 并且 msvc 也有类似的功能。

标签: c++ refcounting


【解决方案1】:

有一个“计数对象”类,在其构造函数和析构函数中进行正确的引用计数,然后派生您想要从中跟踪的对象。然后,您可以使用奇怪的重复模板模式来获取您希望跟踪的任何对象类型的不同计数。

// warning: pseudo code

template <class Obj>
class CountedObj
{
public:
   CountedObj() {++total_;}
   CountedObj(const CountedObj& obj) {if(this != &obj) ++total_;}
   ~CountedObj() {--total_;}

   static size_t OustandingObjects() {return total_;}

private:
   static size_t total_;
};

class MyClass : private CountedObj<MyClass>
{};

【讨论】:

  • 您不应该真的需要在复制 ctor 中进行相同对象检查,因为从自身复制构造对象通常是无效的,因此任何这样做的人都已经在玩愚蠢的游戏了。不过也没什么坏处。插入原子性或锁来品尝,但提问者已经知道了。
  • 我希望有一些不需要永久代码更改的东西,但这是一个不错的解决方案。
  • 没有永久代码更改?你什么意思?您可以使用依赖于assert() 定义的#ifdef NDEBUG 包围递增/递减代码。或者,您可以在布尔值上使用 Trait 来简单地启用或禁用调试或生产的代码。但这不再“简单”了。无论如何,如果您有兴趣,我可以在此处粘贴一段示例代码。然后您需要做的就是定义static const bool debug = truefalse,然后为CountedObj 提供两个模板特化:一个完全为空,一个带有调试代码。
  • @NoSenseEtAl :它使 NoSenseAtAll 有一个正在计数的线程。如果您使用 CountObj,并使其成为您想要跟踪其对象的类的基类,那么在实例化对象的线程的上下文中,计数将增加。
  • 赋值一​​般不会增加对象的数量(拷贝赋值,通过拷贝构造函数会)。
【解决方案2】:

你可以应用这种方法

#ifdef DEBUG

class ObjectCount {
    static int count;
  protected:
    ObjectCount() {
        count++;
    }
  public:
    void static showCount() {
        cout << count;
    }
};

int ObjectCount::count = 0;


class Employee : public ObjectCount {
#else
class Employee {
#endif
  public:
    Employee(){}
    Employee(const Employee & emp) {

    }
};

DEBUG 模式下,调用ObjectCount::showCount() 方法将返回已创建对象的计数。

【讨论】:

    【解决方案3】:

    最好使用内存分析和泄漏检测工具,例如 Valgrind 或 Rational Purify。

    如果你不能并且想要实现自己的机制,那么,

    您应该为您的类重载newdelete 运算符,然后在它们中实现内存诊断。

    查看 this C++ 常见问题解答,了解如何操作以及应采取哪些预防措施。

    【讨论】:

    • FAQ条目难道不是完全忘记提到标准要求operator-new包含一个无限循环以在失败的情况下调用new-handler的要求吗?
    • @Kerrek SB:无限循环?我不确定我是否理解,难道 new 不应该在失败的情况下抛出 std::bad_alloc 吗?
    • 应该有某种无限循环,因为处理程序可能会安装更多处理程序。要么你的新处理程序提供内存而你中断,要么没有新的处理程序而你抛出。
    • 嗯,也许它实际上不在标准中。 3.7.4.1.3 只说你“可以”调用新的处理程序。 Scott Meyers #49 说你应该循环并调用新的处理程序。
    • @Kerrek SB:这是标准规定的还是实施细节?如果它是标准指定的(我不知道,但如果是这样的话会很有趣)您可以在 C++ Lounge 中与 Sbi 讨论它或在常见问题解答中添加评论,(如果你们确实有对话,请在此处添加一个链接,以便我也可以跟进,我在不同的时区并且现在已经过了午夜,所以不能留下来:()
    【解决方案4】:

    这是一个类似的工作示例:http://www.almostinfinite.com/memtrack.html(只需复制页面末尾的代码并将其放入 Memtrack.h,然后运行 ​​TrackListMemoryUsage() 或其他函数之一即可查看诊断)

    它会覆盖 operator new 并执行一些神秘的宏操作,以使其在每个分配中“标记”每个分配的信息,以便计算对象的实例数量以及它们正在使用的内存量。虽然它并不完美,但他们使用的宏在某些条件下会发生故障。如果您决定尝试此操作,请确保将其包含在任何标准标题之后。

    【讨论】:

      【解决方案5】:

      在不了解您的代码和要求的情况下,我看到了 2 个合理的选择:

      a) 使用boost::shared_ptr。它具有您建议的内置原子引用计数,并负责您的内存管理(这样您就不会真正关心查看计数)。其引用计数可通过use_count() 成员获得。

      b) 如果 a) 的含义,比如处理指针和到处都有shared_ptrs,或者可能的性能开销,对你来说是不可接受的,我建议简单地使用可用的内存泄漏检测工具(例如 Valgrind,见上文)将在程序退出时报告您的松散对象。并且没有必要使用侵入性帮助类来(无论如何仅调试)跟踪对象计数,这只会弄乱您的代码,恕我直言。

      【讨论】:

        【解决方案6】:

        我们曾经有一个带有内部计数器的基类的解决方案并从它派生,但我们将其全部更改为 boost::shared_ptr,它保留一个引用计数器并为您清理内存。 boost 智能指针系列非常有用: boost smart pointers

        【讨论】:

          【解决方案7】:

          我的方法,将泄漏计数输出到调试输出(通过我们代码库中实现的 DebugPrint 函数,用您自己的调用替换该调用...)

          #include <typeinfo> 
          #include <string.h>
          class CountedObjImpl
          {
          public:
                  CountedObjImpl(const char* className) : mClassName(className) {}
                  ~CountedObjImpl()
                  {
                          DebugPrint(_T("**##** Leakage count for %hs: %Iu\n"), mClassName.c_str(), mInstanceCount);
                  }
                  size_t& GetCounter() 
                  {
                          return mInstanceCount;
                  }
          
          private:
                  size_t mInstanceCount = 0;
                  std::string mClassName;
          };
          
          template <class Obj>
          class CountedObj
          {
          public:
                  CountedObj() { GetCounter()++; }
                  CountedObj(const CountedObj& obj) { GetCounter()++; }
                  ~CountedObj() { GetCounter()--; }
          
                  static size_t OustandingObjects() { return GetCounter(); }
          
          private:
                  size_t& GetCounter()
                  {
                          static CountedObjImpl mCountedObjImpl(typeid(Obj).name());
                          return mCountedObjImpl.GetCounter();
                  }
          };
          

          示例用法:

          class PostLoadInfoPostLoadCB : public PostLoadCallback, private CountedObj<PostLoadInfoPostLoadCB>
          

          【讨论】:

            【解决方案8】:

            在一些答案中讨论了向单个类添加计数器。但是,它需要选择要计算的类并以一种或另一种方式修改它们。下面的假设是,您正在添加此类计数器以查找某些类的更多对象保持活动状态的错误。

            简要回顾一下已经提到的一些事情:对于真正的内存泄漏,肯定有 valgrind:memcheck 和泄漏清理器。但是,对于没有真正泄漏的其他场景,它们没有帮助(未清除的向量、从未访问过键的映射条目、shared_ptrs 的循环......)。

            但是,由于没有提到这一点:在 valgrind 工具套件中还有 massif,它可以为您提供有关所有已分配内存及其分配位置的信息。但是,让我们假设 valgrind:massif 也不适合您,并且您确实需要实例计数。

            出于偶尔寻找错误的目的 - 如果您愿意接受一些骇人听闻的解决方案并且如果上述选项不起作用 - 您可能会考虑以下事项:如今,堆上的许多对象都由智能指针有效地保存。这可能是标准库中的智能指针类,也可能是您使用的相应帮助库的智能指针类。诀窍如下(以 shared_ptr 为例):您可以通过修补 shared_ptr 实现,即通过将实例计数添加到 shared_ptr 类来一次获取许多类的实例计数器。然后,对于某些 Foo 类,属于 shared_ptr 的计数器将为您指示 Foo 类的实例数。

            当然,它不如直接将计数器添加到相应的类中准确(仅由原始指针引用的实例不计算在内),但对于您的情况来说,它可能已经足够准确了。而且,当然,这不是关于永久更改智能指针类 - 仅在寻找错误期间。至少,智能指针的实现并不太复杂,因此修补它们很简单。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-03-09
              • 1970-01-01
              • 1970-01-01
              • 2016-07-13
              • 2016-03-17
              相关资源
              最近更新 更多