URL
Может быть и бойан, но мне такой синглет нравится больше всех,
особенно глядя вызов метода Singlet().GetValue().
Недостаткиом такого вызова является:
1. Выделяется память под временный объект в стеке.
2. вызываются конструктор и деструктор.

#include <iostream>

using namespace std;

class Singlet {

public:
Singlet() {
cout << "Singlet()" << endl;

}

~Singlet() {
cout << "~Singlet()" << endl;

}

Singlet(const Singlet& object) {}

Singlet& operator=(const Singlet & object) {}

void SetValue(int Value) {
m_Singlet.m_Value = Value;

}

int GetValue(void) {
return m_Singlet.m_Value;

}

void * operator new(size_t count) {

return &m_Singlet;
}

void operator delete(void * object) {

if (object != &m_Singlet) {
delete object;

}
}

private:
static Singlet m_Singlet;
int m_Value;

};
Singlet Singlet::m_Singlet;

int main(int argc, char *argv[]) {

// создаём объект динамически
Singlet * Obj1 = new Singlet();

Obj1->SetValue(10);
cout << Obj1->GetValue() << endl; // устанавливаем значение

// создаём объект динамически
Singlet * Obj2 = new Singlet;
cout << Obj2->GetValue() << endl;

// создаём объект статически
Singlet Obj3;
cout << Obj3.GetValue() << endl;

// создаём объект статически
cout << Singlet().GetValue() << endl;;

return 0;
}


@темы: Трюки программирования


Даже неопытный программист C++ знает, как отслеживать количество существующих объектов класса. Для этого необходимо добавить статическую переменную и немного изменить логику конструктора и диструктора:





#include <iostream>

using namespace std;

class CountedClass {
static int count;
public:
CountedClass () { ++count; }
CountedClass (const CountedClass&;) { ++count; }
~CountedClass() { --count; }
static int getCount() { return count; }
};

int
CountedClass::count = 0;
int main (int argc, char * argv[]) {
CountedClass a;
cout << CountedClass::getCount() << endl; // 1
CountedClass b;
cout << CountedClass::getCount() << endl; // 2
{
CountedClass c(b);
cout << CountedClass::getCount() << endl; // 3
}
cout << CountedClass::getCount() << endl; // 2

return 0;
}




Все конструкторы увеличивают статическую переменную count, а деструкторы уменьшают её. Однако читать дальше





#include <iostream>

using namespace std;

class CountedClass {
static int count;
public:
CountedClass () { ++count; }
CountedClass (const CountedClass&;) { ++count; }
~CountedClass() { --count; }
static int getCount() { return count; }
};
int CountedClass::count = 0;

class CountedClass1 : public CountedClass {};
class CountedClass2 : public CountedClass {};

int main (int argc, char * argv[]) {
CountedClass a;
cout << CountedClass::getCount() << endl; // 1
CountedClass1 b;
cout << CountedClass1::getCount() << endl; // 2 (ошибка)
CountedClass2 c;
cout << CountedClass2::getCount() << endl; // 3 (ошибка)

return 0;
}




Все классы, производные от CountedClass, используют одну статическую переменную, поэтому количество объектов отслеживается сразу по всем классам иерархии.




На первый взгляд решение данной проблемы не представляет трудностей, однако ...




Первое решение данной проблеммы я нашёл в книге Брюса Эккеля и Чака Эллисона "Философия программирования C++, второй том: практическое применение" (Bruce Eckel, Chuck Allison "Thinking in C++, Volume Two: Practical Programming";) У них эта задача решается при помощи шаблоной конструкции, представленной ниже:





#include <iostream>

using namespace std;

template<class T>
class CountedClass {
static int count;
public:
CountedClass () { ++count; }
CountedClass (const CountedClass<T>&;) { ++count; }
~CountedClass() { --count; }
static int getCount() { return count; }
};

template<class T> int CountedClass<T>::count = 0;

class CountedClass1 : public CountedClass<CountedClass1> {};
class CountedClass2 : public CountedClass<CountedClass2> {};

int main (int argc, char * argv[]) {
CountedClass1 a;
cout << CountedClass1::getCount() << endl; // 1
CountedClass2 b;
cout << CountedClass2::getCount() << endl; // 1 (!)

return 0;
}




Здесь ни одна переменная не зависит от типа T, но зато каждый производный класс наследует от уникального базового класса, при определении которого в параметре шаблона указывается сам производный класс.




Всё замечательно и хорошо, но:





#include <iostream>

using namespace std;

template<class T>
class CountedClass {
static int count;
public:
CountedClass () { ++count; }
CountedClass (const CountedClass<T>&;) { ++count; }
~CountedClass() { --count; }
static int getCount() { return count; }
};

template<class T> int CountedClass<T>::count = 0;

class CountedClass1 : public CountedClass<CountedClass1> {};
class CountedClass2 : public CountedClass<CountedClass2> {};
class CountedClass3 : public CountedClass2 {};

int main (int argc, char * argv[]) {
CountedClass1 a;
cout << CountedClass1::getCount() << endl; // 1
CountedClass2 b;
cout << CountedClass2::getCount() << endl; // 1
CountedClass3 c;
cout << CountedClass3::getCount() << endl; // 2 (опять ошибка)

return 0;
}




Дальнейшее наследование опять приводит к ошибке подсчёта количества экземпляров производного класса.





Я описал проблемму моему колеге и товарищу Шмелёву Сергею Игорьевичу, и он предложил следующее решение:





//------------------------------------------------------------
#include <memory>
class InstanceCounterBase {
public:
virtual void SubOne() = 0;
static std::auto_ptr<InstanceCounterBase> m_PrevCounter;
};
std::auto_ptr<InstanceCounterBase> InstanceCounterBase::m_PrevCounter;
//------------------------------------------------------------
template <class T>
class InstanceCounter: InstanceCounterBase {
private:
InstanceCounter() { ++m_Counter; }
void SubOne() { --m_Counter; }
public:
static int Value() { return m_Counter; }
static void Install(bool Base = false) {
if (Base) { m_PrevCounter.release(); }
if (m_PrevCounter.get()) { m_PrevCounter->SubOne(); }
m_PrevCounter.reset(new InstanceCounter<T>());
}
private:
static int m_Counter;
};
template <class T> int InstanceCounter<T>::m_Counter = 0;
//------------------------------------------------------------




Счетчик устанавливается на объект класса в конструкторе вызовом метода Install. При установке счетчика на базовый класс необходимо вызвать метод Install с параметром true, что является для счетчика признаком базового класса.





//------------------------------------------------------------
class Base {
public:
Base() {
InstanceCounter<Base>::Install(true); // пометка базового класса
}
};
class Derived: virtual public Base {
public:
Derived(): Base() {
InstanceCounter<Derived>::Install();
}
};
class Derived2: virtual public Base {
public:
Derived2(): Base() {
InstanceCounter<Derived2>::Install();
}
};
class Derived2_2: public Derived2 {
public:
Derived2_2(): Derived2() {
InstanceCounter<Derived2_2>::Install();
}
};
class SuperDerived: public Derived, public Derived2 {
public:
SuperDerived(): Derived(), Derived2() {
InstanceCounter<SuperDerived>::Install();
}
};
class Another {
public:
Another() {
InstanceCounter<Another>::Install(true); // другой базовый класс
}
};
//------------------------------------------------------------

#include <iostream>

int main() {
Derived D1, D2, D3;
Base B1, B2;
Another A1, A2;
Derived2_2 D2_21;
SuperDerived SD1;
std::cout << "Base #" << InstanceCounter<Base>::Value() << std::endl;
std::cout << "Derived #" << InstanceCounter<Derived>::Value() << std::endl;
std::cout << "Derived2 #" << InstanceCounter<Derived2>::Value() << std::endl;
std::cout << "Derived2_2 #" << InstanceCounter<Derived2_2>::Value() << std::endl;
std::cout << "SuperDerived #" << InstanceCounter<SuperDerived>::Value() << std::endl;
std::cout << "Another #" << InstanceCounter<Another>::Value() << std::endl;
return 0;
}
//------------------------------------------------------------




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





//------------------------------------------------------------
Base #2
Derived #3
Derived2 #0
Derived2_2 #1
SuperDerived #1
Another #2
//------------------------------------------------------------




Все правильно, и при обычном наследовании счетчик работает корректно. Счетчик корректно работает и при виртуальном наследовании. Для множественного наследования такой счетчик работает при условии, что иерархия классов имеет один базовый класс.





Суть подхода:

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





Но, данный подход имеет несколько подводных камней:






  1. В приведенной реализации счетчика объектов используется соглашение, что базовый класс отмечается разработчиком.




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








Взяв за основу метода Шмелёва С.И. (базовый класс передаёт порождённому указатель на свой счётчик, затем порождённый уменьшает счётчик базового на единицу и в конце увеличивает свой счётсик на единицу) я решил написать свой метод.




Принцип работы и все объяснения находятся в коментария.





/**
* @class InstanceCounterBase
* Класс счётчик.
*/

class InstanceCounterBase {
protected:
int m_Count;
public:
void Dec(void) {
m_Count--;
}
void Inc(void) {
m_Count++;
}
int Count(void) { return m_Count; }
};

/**
* Данный шаблон представляет собой шаблон синглетного класса наследованный от класса счётчика.
* Презназначен для получения единственного счётчика для каждого из классов.
*/

template <class T>
class InstanceCounter : public InstanceCounterBase {
InstanceCounter(void) { m_Count = 0; }
InstanceCounter(const InstanceCounter&);
void operator=(const InstanceCounter&);
public:
static InstanceCounter * Instance(void) {
static InstanceCounter InstanceCounterObj;
return &InstanceCounterObj;
}
};

/**
* @class Counter
* Базовый класс иерархии классов, объекты которых будут подсчитываться.
* Класс Counter содержит указатель InstanceCounterBase * m_PrevCounter
* - указатель на предыдущий счётчик, таким образом он будет присутствовать во всех
* пораждённых классах в иерархии. В каждом конструкторе необходимо вставить
* строчку "Install<тип класса>();". Во время создания объекта класса, в порождённых
* классах будет декрементироваться счётчик базового или предыдущего класса,
* указателю m_PrevCounter присвается адрес собственного счётчика, после чего инкрементироваться
* счётчик собственного класса. Следовательно счётчик, который был увеличен базовым классом,
* будет уменьшен порождённым и инкрементированным останется только последний класс в иерархии.
* При разрушении объекта, когда дойдёт дело до деструктора класса Counter, то указатель
* m_PrevCounter будет содержать указатель на счётчик последнего класса иерархии
* и декрементирует его.
*/

class Counter {
protected:
InstanceCounterBase * m_PrevCounter;
public:
Counter(void) {
m_PrevCounter = InstanceCounter<Counter>::Instance();
m_PrevCounter->Inc();
}
~Counter(void) {
m_PrevCounter->Dec();
}
template <class T> void Install(void) {
m_PrevCounter->Dec();
m_PrevCounter = InstanceCounter<T>::Instance();
m_PrevCounter->Inc();
}
};

/**
* Отладочный макрос.
* Выводит на консоль количество объектов класса type.
*/

#define PRINT_COUNTOF(type) \
std::cout << "Count of '" << #type << "':" << InstanceCounter<type>::Instance()->Count() << std::endl


/**
* Данный макрос предназначен для более компактной и понятной записи.
* @return - возвращает количество объектов класса type.
*/
#define COUNTOF(type) InstanceCounter<type>::Instance()->Count()




Пример использования:





class Base : public Counter {
public:
Base() {
Install<Base>();
}
};

class Derived1 : virtual public Base {
public:
Derived1() {
Install<Derived1>();
}
};

class Derived2 : virtual public Base {
public:
Derived2() {
Install<Derived2>();
}
};

class MultiDerived : virtual public Derived1, Derived2 {
public:
MultiDerived() {
Install<MultiDerived>();
}
};

int main(void) {

PRINT_COUNTOF(Counter);
PRINT_COUNTOF(Base);
PRINT_COUNTOF(Derived1);
PRINT_COUNTOF(Derived2);
PRINT_COUNTOF(MultiDerived);
stt::cout << "==================" << stt::endl;

{
Derived1 dev1;
Derived2 dev2, dev3;
Counter cnt;
Base base, base2, base3;
MultiDerived mul, mul2, mul3, mul4;

PRINT_COUNTOF(Counter);
PRINT_COUNTOF(Base);
PRINT_COUNTOF(Derived1);
PRINT_COUNTOF(Derived2);
PRINT_COUNTOF(MultiDerived);
stt::cout << "==================" << stt::endl;
}

PRINT_COUNTOF(Counter);
PRINT_COUNTOF(Base);
PRINT_COUNTOF(Derived1);
PRINT_COUNTOF(Derived2);
PRINT_COUNTOF(MultiDerived);

return 0;
}




Экранный вывод:





Count of 'Counter':0
Count of 'Base':0
Count of 'Derived1':0
Count of 'Derived2':0
Count of 'MultiDerived':0
==================
Count of 'Counter':1
Count of 'Base':3
Count of 'Derived1':1
Count of 'Derived2':2
Count of 'MultiDerived':4
==================
Count of 'Counter':0
Count of 'Base':0
Count of 'Derived1':0
Count of 'Derived2':0
Count of 'MultiDerived':0




Достоинства данного подхода:






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




  • Во вторых, в методе Шмелёва С.И. в каждом конструкторе вызывается 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<тип класса>();", и если хоть в одном конструкторе будет пропущена данная строчка, то подсчёт колчества объектов базового класса будет не коректен.





Но, как известно, нет предела совершенству и всё гениальное просто, посидев и подумав, предыдущая реализация превратилась в более компактный, понятный и красивый код (с доводкой до совершенства с помощью Шмелёва С.И.):





/**
* @class Counter
* Базовый класс иерархии классов, объекты которых будут подсчитываться.
* Класс Counter содержит указатель InstanceCounterBase * m_PrevCounter
* - указатель на предыдущий счётчик, таким образом он будет присутствовать во всех
* пораждённых классах в иерархии. В каждом конструкторе необходимо вставить
* строчку "Install<тип класса>();". Во время создания объекта класса, в порождённых
* классах будет декрементироваться счётчик базового или предыдущего класса,
* указателю m_PrevCounter присвается адрес собственного счётчика, после чего инкрементироваться
* счётчик собственного класса. Следовательно счётчик, который был увеличен базовым классом,
* будет уменьшен порождённым и инкрементированным останется только последний класс в иерархии.
* При разрушении объекта, когда дойдёт дело до деструктора класса Counter, то указатель
* m_PrevCounter будет содержать указатель на счётчик последнего класса иерархии
* и декрементирует его.
*/

class Counter {
template <class T> struct CounterBase {
static int m_Count;
};
int * m_PrevCounter;
public:
Counter(void): m_PrevCounter(&(++CounterBase<Counter>::m_Count)) {}
~Counter(void) {
--(*m_PrevCounter);
}
template <class T> void Install(void) {
--(*m_PrevCounter);
m_PrevCounter = &(++CounterBase<T>::m_Count);
}
template <class T> static int Count(void) {
return CounterBase<T>::m_Count;
}
};
template <class T> int Counter::CounterBase<T>::m_Count = 0;

/**
* Данный макрос предназначен для более компактной и понятной записи.
* @return - возвращает количество объектов класса type.
*/

#define COUNTOF(type) Counter::Count<type>()