особенно глядя вызов метода Singlet().GetValue().
Недостаткиом такого вызова является:
1. Выделяется память под временный объект в стеке.
2. вызываются конструктор и деструктор.
#include <iostream> |
Даже неопытный программист C++ знает, как отслеживать количество существующих объектов класса. Для этого необходимо добавить статическую переменную и немного изменить логику конструктора и диструктора:
|
Все конструкторы увеличивают статическую переменную count, а деструкторы уменьшают её. Однако читать дальше
|
Все классы, производные от CountedClass, используют одну статическую переменную, поэтому количество объектов отслеживается сразу по всем классам иерархии.
На первый взгляд решение данной проблемы не представляет трудностей, однако ...
Первое решение данной проблеммы я нашёл в книге Брюса Эккеля и Чака Эллисона "Философия программирования C++, второй том: практическое применение" (Bruce Eckel, Chuck Allison "Thinking in C++, Volume Two: Practical Programming" У них эта задача решается при помощи шаблоной конструкции, представленной ниже:
|
Здесь ни одна переменная не зависит от типа T, но зато каждый производный класс наследует от уникального базового класса, при определении которого в параметре шаблона указывается сам производный класс.
Всё замечательно и хорошо, но:
|
Дальнейшее наследование опять приводит к ошибке подсчёта количества экземпляров производного класса.
Я описал проблемму моему колеге и товарищу Шмелёву Сергею Игорьевичу, и он предложил следующее решение:
|
Счетчик устанавливается на объект класса в конструкторе вызовом метода Install. При установке счетчика на базовый класс необходимо вызвать метод Install с параметром true, что является для счетчика признаком базового класса.
|
В примере создана некоторая иерархия классов, создано по несколько объектов каждого класса и результат подсчета количества объектов выведен на экран:
|
Все правильно, и при обычном наследовании счетчик работает корректно. Счетчик корректно работает и при виртуальном наследовании. Для множественного наследования такой счетчик работает при условии, что иерархия классов имеет один базовый класс.
Суть подхода:
Каждый конструктор порождённого класса не только увеличивает счётчик экземпляров собственного класса, но и уменьшает счётчик базового класса. Для уменьшения счётчика базового класса служит статическая переменная m_PrevCounter.
Но, данный подход имеет несколько подводных камней:
В приведенной реализации счетчика объектов используется соглашение, что базовый класс отмечается разработчиком.
Данный подход может использоваться только в однопоточных приложениях, т.к. переменная m_PrevCounter статическая, следовательно если в одном потоке конструктор базового класса присвоит переменной m_PrevCounter указатель на счётчик объектов собственного класса, чтобы производный его уменьшил, но произошло переключение на другой поток и там конструктор другого класса, даже может быть другой иерархии в собственном конструкторе перезапишет указатель m_PrevCounter на свой счётчик, следовательно при возврате к первому потоку m_PrevCounter будет содержать указатель на неизвестный счётчик объектов.
Взяв за основу метода Шмелёва С.И. (базовый класс передаёт порождённому указатель на свой счётчик, затем порождённый уменьшает счётчик базового на единицу и в конце увеличивает свой счётсик на единицу) я решил написать свой метод.
Принцип работы и все объяснения находятся в коментария.
|
Пример использования:
|
Экранный вывод:
|
Достоинства данного подхода:
Данный метод позволяет работать в потоках, т.к. каждый объект содержит собственный указатель на счётчик базового класса.
Во вторых, в методе Шмелёва С.И. в каждом конструкторе вызывается new и delete, а эти операции для операционной системы довольно-таки трудоёмкие и потребляют ресурсы, и чем сложнее иерархия классов тем больше накладных рассходов больше, в моём подходе в каждом конструкторе всего лишь декримент счётчика базового класса m_PrevCounter->Dec() (причём это inline функция, которая замещается на m_PrevCounter->m_Count--), затем перезаписывается указатель на счётчик объектов собственного класса m_PrevCounter = InstanceCounter<T>::Instance() и инкремент счётчика объектов собственного класса m_PrevCounter->Inc() (который заменится на m_PrevCounter->m_Count++).
И наконец, в логике моего подхода есть приимущество, т.к. у Шмелёва С.И. надо указывать true для базового класса, и где-нибудъ можно ошибившись случайно указать true, с моей же точки зрения наследование более логично для указания базового класса.
Из недостатков хочется отметить необходимость в каждом конструкторе каждого класса вставлять строчку "Install<тип класса>();", и если хоть в одном конструкторе будет пропущена данная строчка, то подсчёт колчества объектов базового класса будет не коректен.
Но, как известно, нет предела совершенству и всё гениальное просто, посидев и подумав, предыдущая реализация превратилась в более компактный, понятный и красивый код (с доводкой до совершенства с помощью Шмелёва С.И.):
|