Самый простой способ подсчета экземпляров объекта

Я хотел бы знать точное количество экземпляров определенных объектов, выделенных в определенный момент выполнения. В основном для поиска возможных утечек памяти (в основном я использую RAII, почти ничего нового, но все же я мог забыть .clear() для вектора перед добавлением новых элементов или чего-то подобного). Офк я мог бы иметь

atomic<int> cntMyObject;

что я -- в деструкторе, ++ увеличение в конструкторе, cpy конструктор (надеюсь, я все рассмотрел :)). Но это жесткое кодирование для каждого класса. И не просто отключить его в режиме "Release". Итак, есть ли простой элегантный способ, который можно легко отключить для подсчета экземпляров объекта?


person NoSenseEtAl    schedule 17.08.2011    source источник
comment
Почему бы не использовать профиль для поиска утечек памяти?...   -  person    schedule 17.08.2011
comment
Не оператор присваивания - он не изменяет количество существующих объектов типа, просто изменяет значение одного из них.   -  person Steve Jessop    schedule 17.08.2011
comment
Как бы я ни находил идею добавления глобального счетчика объектов интересной, я бы сказал, что для вашей практической проблемы устранения ошибок памяти запуск вашей программы через Valgrind был бы гораздо более доступным решением, к тому же с более содержательными сообщениями.   -  person Kerrek SB    schedule 17.08.2011
comment
Если вы думаете, что-то вроде Google HEAPProfiler - это хорошо, но однажды я испортил материал STL (не потокобезопасные обновления), и он не обнаружил утечек (это помогло мне найти их, так как я заметил, что круги становятся больше для перезагрузки одного и того же материала) . Также я не знаю, как сосредоточиться на определенном классе в HeapProfiler, я знаю только, как генерировать использование памяти всей программы.   -  person NoSenseEtAl    schedule 17.08.2011
comment
Готовы ли вы принять решения, специфичные для компилятора? Я могу предложить ненавязчивый gcc, и аналогичная функция есть и для msvc.   -  person Flexo    schedule 20.08.2011
comment
Да, я использую gcc(g++), но я всегда предпочитаю стандартные решения   -  person NoSenseEtAl    schedule 22.08.2011
comment
Использование профилировщика здесь не выход. Как упомянул спрашивающий, забыть о vector.clear() всегда будет проблемой. В противном случае не было бы утечек памяти в Java-программах. Это не типичные утечки C++, но это важная проблема, и для ее решения требуется совершенно другой инструмент, чем valgrind и т. Д.   -  person Lothar    schedule 02.08.2016


Ответы (8)


Имейте класс "подсчитываемых объектов", который выполняет правильный подсчет ссылок в своих конструкторах и деструкторах, а затем извлекаете из него свои объекты, которые вы хотите отслеживать. Затем вы можете использовать любопытно повторяющийся шаблон шаблона, чтобы получить различные подсчеты для любых типов объектов, которые вы хотите отслеживать.

// 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>
{};
person Chad    schedule 17.08.2011
comment
Вам действительно не нужна проверка одного и того же объекта в копировщике, поскольку обычно недопустимо копировать-конструировать объект из самого себя, поэтому любой, кто это делает, уже играет в глупые игры. Зато не наносит никакого вреда. Вставляйте атомарность или блокировки по вкусу, но вопрошающий и так об этом знает. - person Steve Jessop; 17.08.2011
comment
Я надеялся на что-то, что не требует постоянного изменения кода, но это хорошее решение. - person NoSenseEtAl; 17.08.2011
comment
Без постоянного изменения кода? Что ты имеешь в виду? Вы можете просто окружить код увеличения/уменьшения #ifdef NDEBUG, который зависит от определения assert(). Или вы можете использовать Trait для логического значения, чтобы просто включить или отключить код для отладки или производства. Но это уже не просто. Если вам все равно интересно, я могу вставить кусок кода здесь. Все, что вам нужно сделать, это определить либо static const bool debug = true, либо false, и предоставить две специализации шаблона для CountedObj: один полностью пустой, другой с отладочным кодом. - person towi; 20.08.2011
comment
Дох, я такой нуб, теперь все ясно. Кстати, вы знаете, какой самый простой способ сделать регистрацию счетчиков, также содержащихся в базовом классе? Я думал о запуске потока в первый раз при создании класса и о том, чтобы этот поток выполнял подсчет. - person NoSenseEtAl; 22.08.2011
comment
При использовании окон у вас возникнут проблемы, если ваши объекты находятся в нескольких разных dll из-за способа управления статическими переменными, одной копии dll и одной копии в исполняемой программе. - person Pedro Ferreira; 23.08.2011
comment
@NoSenseEtAl: это делает NoSenseAtAll иметь поток, который выполняет подсчет. Если вы используете CountObj и делаете его базовым классом для классов, объект которых вы хотите отслеживать, то в контексте потока, в котором был создан экземпляр объекта, счетчик будет увеличиваться. - person Ajeet Ganga; 25.08.2011
comment
Сравнение для этого != &obj действительно не нужно в ctor копирования, так как это всегда новый объект. - person Simon; 25.08.2011
comment
@Ajeet, извините, последним словом должно быть ведение журнала, не считая. - person NoSenseEtAl; 26.08.2011
comment
if(this != &obj) -- как это когда-либо могло быть оценено как ложное? - person ildjarn; 31.08.2011
comment
@Chad: Ну, остальная часть кода была идеальной, поэтому я подумал, что, может быть, я просто что-то упустил. :-] - person ildjarn; 01.09.2011
comment
А оператор присваивания? Разве присваивание не должно также увеличивать total_? - person Flow; 21.02.2014
comment
Присваивание обычно не увеличивает количество объектов (присваивание копирования через конструктор копирования). - person Chad; 21.02.2014

Лучше использовать инструменты профилирования памяти и обнаружения утечек, такие как Valgrind или Rational Purify.

Если вы не можете и хотите реализовать свой собственный механизм,

Вы должны перегрузить операторы new и delete для своего класса, а затем реализовать в них диагностику памяти.

Взгляните на этот ответ на часто задаваемые вопросы C++, чтобы узнать, как это сделать что и какие меры предосторожности вы должны принять.

person Alok Save    schedule 17.08.2011
comment
Разве в записи часто задаваемых вопросов не забывается упомянуть требование, согласно которому оператор new должен содержать бесконечный цикл, вызывающий обработчик new в случае сбоя? - person Kerrek SB; 17.08.2011
comment
@Kerrek SB: Бесконечный цикл? Я не уверен, что понимаю. Разве new не должен просто бросать std::bad_alloc в случае сбоя? - person Alok Save; 17.08.2011
comment
Должен быть какой-то бесконечный цикл, так как обработчики могут устанавливать дополнительные обработчики. Либо ваш новый обработчик предоставляет память, и вы ломаетесь, либо нового обработчика больше нет, и вы бросаете. - person Kerrek SB; 17.08.2011
comment
Хм, может быть, это на самом деле не в стандарте. 3.7.4.1.3 только говорит, что вы можете вызвать новый обработчик. Scott Meyers #49 говорит, что вы должны зацикливаться и вызывать новые обработчики. - person Kerrek SB; 17.08.2011
comment
@Kerrek SB: это указано в Стандарте или в деталях реализации? Если это указано в стандарте (я не знаю об этом, но было бы интересно, если бы это было так), вы можете обсудить это с Sbi в C++ Lounge или добавить комментарий к ответу на часто задаваемые вопросы (если у вас есть разговор, пожалуйста, сделайте добавьте ссылку сюда, чтобы я тоже мог следить, я нахожусь в другом часовом поясе, и для меня уже далеко за полночь, так что не могу оставаться в стороне :() - person Alok Save; 17.08.2011

вы можете применить этот подход

#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() вернет количество созданных объектов.

person sukumar    schedule 26.08.2011

Это своего рода рабочий пример чего-то подобного: http://www.almostinfinite.com/memtrack.html (просто скопируйте код в конце страницы и поместите его в Memtrack.h, а затем запустите TrackListMemoryUsage() или одну из других функций для просмотра диагностики)

Он переопределяет оператор new и выполняет некоторые загадочные макросы, чтобы «помечать» каждое выделение информацией, позволяющей подсчитать, сколько экземпляров объекта и сколько памяти они используют. Однако это не идеально, макросы, которые они используют, ломаются при определенных условиях. Если вы решите попробовать это, обязательно включите его после любых стандартных заголовков.

person Alex    schedule 17.08.2011

Не зная вашего кода и ваших требований, я вижу 2 разумных варианта:

а) Используйте boost::shared_ptr. Он имеет встроенные атомарные счетчики ссылок, которые вы предложили, и заботится об управлении вашей памятью (так что вам никогда не захочется смотреть на счетчик). Его счетчик ссылок доступен через элемент use_count().

б) Если последствия а), такие как работа с указателями и наличие shared_ptrs везде, или возможные потери производительности, для вас неприемлемы, я бы предложил просто использовать доступные инструменты для обнаружения утечек памяти (например, Valgrind, см. выше), который сообщит о незакрепленных объектах при выходе из программы. И нет необходимости использовать навязчивые вспомогательные классы для (во всяком случае, только для отладки) отслеживания количества объектов, которые просто испортят ваш код, ИМХО.

person awx    schedule 26.08.2011

Раньше у нас было решение базового класса с внутренним счетчиком и производным от него, но мы изменили все это на boost::shared_ptr, он хранит счетчик ссылок и очищает память для вас. Семейство интеллектуальных указателей boost весьма полезно: ускорить интеллектуальные указатели

person Pedro Ferreira    schedule 23.08.2011

Мой подход, который выводит количество утечек в вывод отладки (через функцию 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>
person Larry    schedule 21.11.2017

Добавление счетчиков к отдельным классам обсуждалось в некоторых ответах. Однако для этого требуется выбрать классы для подсчета и изменить их тем или иным образом. Далее предполагается, что вы добавляете такие счетчики для поиска ошибок, при которых сохраняется больше объектов определенных классов, чем ожидалось.

Кратко повторим некоторые уже упомянутые вещи: для реальных утечек памяти, безусловно, есть valgrind:memcheck и дезинфицирующие средства для утечек. Однако для других сценариев без реальных утечек они не помогают (неочищенные векторы, записи карты с никогда не доступными ключами, циклы shared_ptrs, ...).

Но, так как об этом не упоминалось: В наборе инструментов valgrind также есть массив, который может предоставить вам информацию обо всех кусках выделенной памяти и о том, где они были выделены. Однако давайте предположим, что valgrind:massif также не подходит для вас, и вам действительно нужны подсчеты экземпляров.

В целях случайного поиска ошибок - если вы открыты для какого-то хакерского решения и если вышеперечисленные варианты не работают - вы можете рассмотреть следующее: В настоящее время многие объекты в куче эффективно удерживаются интеллектуальными указателями. Это могут быть классы интеллектуальных указателей из стандартной библиотеки или классы интеллектуальных указателей соответствующих вспомогательных библиотек, которые вы используете. Хитрость заключается в следующем (выбрав в качестве примера shared_ptr): вы можете получить счетчики экземпляров для многих классов одновременно, исправив реализацию shared_ptr, а именно, добавив счетчики экземпляров в класс shared_ptr. Затем для некоторого класса Foo счетчик, принадлежащий shared_ptr‹Foo›, покажет вам количество экземпляров класса Foo.

Конечно, это не так точно, как добавление счетчиков непосредственно в соответствующие классы (экземпляры, на которые ссылаются только необработанные указатели, не учитываются), но, возможно, это достаточно точно для вашего случая. И, конечно же, речь не идет о постоянном изменении классов умных указателей — только во время поиска ошибок. По крайней мере, реализации интеллектуальных указателей не слишком сложны, поэтому исправить их несложно.

person Dirk Herrmann    schedule 26.09.2020