for (int iCounter = 0; iCounter ‹ iTop; iCounter ++)
if (*(array+iCounter)!= NULL) delete *(array+iCounter);
delete[] array;
}
// Управление стеком
// Направить указатель в стек
void push (Type* _t) { array[iTop++]=_t; }
// Вынуть указатель из стека
Type* pop (void) {
if (iTop == 0) return NULL;
else return array[--iTop];
}
// Стек пуст?
int isEmpty (void) { return iTop==0; }
// Очистить стек
void emptyAll (void) {
for (int iCounter = 0; iCounter ‹ iTop; iCounter ++)
if (*(array+iCounter)!= NULL) delete *(array+iCounter);
iTop = 0;
}
};
// Шаблон класса с многоуровневой отменой
template ‹class Type›
class MLTrans {
typedef ampstack‹Type› stack;
private:
Type* that; // Текущее значение
stack history; // контейнер предыдущих значений
public:
// конструктор-деструктор
MLTrans: that(new Type) {}
~MLTrans { delete that; }
// Сохранение текущего значения, aналог SAVE TRANSACTION в SQL серверах
void Push {
history.push(that);
that = new Type(*that);
}
// удаление промежуточных состояний
void Commit { history.emptyAll; }
// Откат на одну позицию; уничтожает текущее значение.
void PopOne {
if (!history.isEmpty) {
delete that;
that = history.pop;
}
}
// Откат к началу транзакции.
void Rollback {
Type* old = history.pop;
Type* older = NULL;
if (old!= NULL) {
while ((older = history.pop)!= NULL) {
delete old;
old = older;
}
delete that;
that = old;
}
}
// Переопределенный operator-›
Type* operator-› { return that; }
}
// проверим работу
int main {
int t;
MLTrans‹CType› a;
a-›set(5);
t = a-›get;
a.Push;
a-›set(6);
t = a-›get;
a.Push;
t = a-›get;
a-›set(7);
t = a-›get;
a.Push;
t = a-›get;
a-›set(9);
t = a-›get;
// a.Push;
t = a-›get;
a.PopOne;
t = a-›get;
a.Rollback;
t = a-›get;
return 0;
}
Шаг 24 - Как создавать ТОЛЬКО локальные переменные.
В Шаге 17 мы изыскали способ подавить создание локальных переменных. Решим обратную задачу - как подавить иные способы их создания. А какие иные? Любые другие способы предполагают вызов оператора operator new для выделения памяти и потом вызов конструктора. Значит, надо объявить operator new закрытым членом класса, да и все. Ничего в нем делать не надо, а сразу назад. Попробуем?
class CNoHeap {
public:
int a;
private:
void* operator new(size_t size) { return NULL; }
};
int main {
/*
CNoHeap* firstTestNoHeap = new CNoHeap; // Не откомпилируется
*/
CNoHeap secondTestNoHeap; // А это пожалуйста.
return 0;
}
Теперь, если определить макрос:
#define DECLARE_LOCAL \
private: \
void* operator new(size_t size) { return NULL; }
и потом вкладывать его во всякие разные объекты, отвечающие за захват и освобождение ресурсов, то получится весьма удобно; Вы ГАРАНТИРОВАННО освободите любые ресурсы, захваченные в конструкторе и освобождаемые в деструкторе, в том числе в исключении. В любом случае, всякое ограничение уменьшает энтропию.
Для Шага 17, где мы рисовали производящие и разрушающие функции, тоже можно нарисовать макрос… и назвать его DECLARE_DYNCREATE. То есть, я хочу сказать, что Вы можете аккуратно переписать нужное из него в свою версию, а в результате получите
class CSomeClass {
DECLARE_NOLOCAL
public:
bool Initialize (param list);
};
И это будет уже иметь определенный Вами набор функций, возможно, включая конструкторы и деструктор.
Шаг 25 - Как сделать виртуальной свободную функцию.
Чаще всего этот прием я видел в отношении оператора operator‹‹. Точнее, не чаще, а всегда. На нем и разберем. Пусть у нас есть иерархия классов, и мы хотим определить диагностическую функцию Dump. Она должна вываливать диагностику в заданное что-то (CDestination). У нас есть два варианта: или сделать функцию виртуальной в иерархии классов: