Albert Makhmutov - Идиомы и стили С++. Страница 13

Сначала, кто такой operator-›*. Это который вызывает функцию-член по указателю. Такую функцию нужно вызывать с указанием объекта, если из другой функции-члена, то в виде (this-›*mpf)() или (*this).*mpf().

// Этот класс используется так же дальше

class CSmth {

public:

 int a;

 int pf (void) {return a;}

};

typedef int (CSmth::*PF)(void);

Если мы нарисуем умный указатель на объект класса CSmth, определять operator-›*() нужно самостоятельно. Что он должен вернуть? Нечто такое, к чему можно применить operator(). То есть, это снова proxy-объект. Мейерс называет его "незавершенный вызов функции-члена" (Pending Member Function Calling). Он должен знать, к какому объекту применяется, и знать об указателе на функцию, то есть он должен иметь в себе указатели на них обоих, и инициализировать их в конструкторе. А operator() должен возвращать уже нужный нам int, или все что угодно другое, что может вернуть указываемая функция.

// класс незавершенного вызова. Это самое важное.

class pmfc {

private:

 // два указателя - на объект и на функцию

 CSmth* m_smth;

 PF m_pfunct;

public:

 // конструктор

 pmfc (CSmth*& _smth, PF& _pfunct) : m_smth(_smth), m_pfunct(_pfunct) {}

 // вызов конечной функции из оператора ()

 int operator()() const { return (m_smth-›*m_pfunct)(); }

};

// класс умного указателя.

class CPtr {

private:

 CSmth* a;

public:

 CPtr() { a = new CSmth(); }

 ~CPtr() { delete a; }

 CSmth* operator-›() const { return a; }

 CSmth& operator* () const { return *a; }

 operator CSmth* () const { return a; }

 // возвращает PMFC. Это тоже важно.

 pmfc operator-›*(PF _pf) { return pmfc (a, _pf); }

};

// проверим все

int main() {

 CPtr t;

 t-›a = 10;

 // заодно проверим operator*

 (*t).a = 16;

 int b = 0;

 // получили указатель на функцию.

 PF lpF =&CSmth::pf;

 // вызвали функцию по указателю при помощи нашей конструкции

 b = (t-›*lpF)();

 return 0;

}

С тоской взглянув на полученный результат, сразу осознаешь, что без шаблонов не обойтись - ведь нужно обслуживать разные типы указателей на функции. Но зато мы минимум знаем, как решать эту проблему. Еще раз испытали proxy-объекты. Потрогали указатели на функции и функции члены. Перегрузили операторы * и (). И если встанет проблема - то знаем, где искать решение (у Скотта Мейерса).

Шаг 28 - Классы объектов, поддерживающие транзакции. Продолжение 2.

Классы объектов, хранящие состояния, получились очень неплохие - при минимальных интеллектуальных затратах, хотя о транзакциях говорить рано: для транзакций они недостаточно кислотные. (ACID - Atomic, Consistent, Isolated, Durable). Не хватает вот чего:

1. Объекты, задействованные в транзакции, блокируются на запись.

2. Объекты, задействованные в транзакции, представляют другим клиентам свое состояние до транзакции.

Мы уже понимаем общий принцип: если нужна дополнительная логика - вынесите ее на отдельный уровень. Что означает это в нашем случае? То, что 1: транзакция должна быть представлена отдельным уровнем - отдельным классом; 2: объекты, задействованные в транзакции, должны поддерживать специальный стандартный интерфейс, за который транзакция должна ими рулить. То есть, они либо должны быть порождены от специального абстрактного базового класса, либо они должны быть упакованы в специальный смарт-указатель - делающий то же самое.

Все остальное - дело техники. Сразу поясняю код: класс CLockable (базовый) содержит указатель на транзакцию, к которой принадлежит в данный момент, а так же чистые виртуальные функции rollback и commit, назначение которых очевидно. Класс CTransaction представляет собой транзакцию, и содержит в себе список задействованных объектов - затем чтобы роллбачить или коммитить их все вместе, да чтоб на ходу можно проверить - принадлежит ли объект некоей транзакции или нет. Функция addLock() добавляет объект к транзакции, то есть распространяет свое действие на него, тем самым блокируя изменения со стороны других клиентов. После использования объекты автоматически разрегистрируются.

Хочу еще раз напомнить о принципе, который в этом Шаге формулируется первый раз: если существует определенная, законченная логика взаимодействия объектов или систем - она выносится на отдельный уровень. Поддержка транзакций очевидно является законченной бизнес-логикой, и, следовательно, должна быть вынесена на специальный уровень. Пусть классы реализуют свою функциональность, не заботясь о свопе, многозадачности-поточности, транзакциях, доступе, приоритетах, авторизациях, синхронизации, сборке мусора (garbage collection) или уплотнении памяти - это не его проблемы. Это - другие уровни, которыми занимаются другие классы, или даже системы. В современном компьютерном мире этот принцип сегодня доминирует, именно на него работают новомодные COM+ с MTS, IBM-websphera, JavaBeans, и множество иных. То, что грамотная корпоративная система должна работать в минимум четырех уровнях, уже является прописной истиной: данные, бизнес-логика сервера, бизнес-логика клиента, клиент.

Понимаете теперь, в чем преимущество смарт-указателей? Они позволяют с легкостью создавать новые уровни бизнес-логик без привлечения дополнительных средств и схем, а едиными только средствами языка. Попробуйте сделать что-либо подобное на языке, который не поддерживает указателей и перегрузки операторов (да шаблоны еще)! Вот код.

#include "ampstack.h"

// Абстрактный базовый класс

class CLockable {

 friend class CTransaction;

protected:

 // текущая транзакция, если есть

 CTransaction* m_trans;

public:

 CLockable (): m_trans (NULL) {}

 // регистрируемся в какой-то транзакции

 int regObj (CTransaction* _pt);

 // и разрегистрируемся

 void unregObj();

 virtual ~CLockable() {}

 virtual void rollback () =0;

 virtual void commit() =0;

};

// Класс транзакции

class CTransaction {

 friend class CLockable;

private:

 // коллекция зарегистрированных объектов

 ampstack‹CLockable› m_locks;

 // добавить объект к транзакции

 void addLock (CLockable*);

public:

 virtual ~CTransaction ();

// закрепить или отменить все изменения во всех

// зарегистрированных объектах.

 void commit();

 void rollback();

// проверить, зарегистрирован ли объект в этой транзакции

 int allready_locked(CLockable*);

};

// зарегистрироваться в транзакции

inline int CLockable::regObj (CTransaction* _pt) {

 if (m_trans!= NULL) return 0;

 else {

  _pt-›addLock(this);

  m_trans = _pt;

  return 1;

 }

}

// разрегистрироваться

inline void CLockable::unregObj() {

 m_trans = NULL;

}

// добавление объекта к транзакции.

inline void CTransaction::addLock(CLockable* _lc) {

 // а именно, воткнуть указатель на него в стек.

 m_locks.push (_lc);

}

// закрепление всех объектов

void CTransaction::commit() {

 // создаем итератор

 ampIter‹CLockable› it(&(this-›m_locks));

 // пробежались по всем, закрепились.

 it.goStart();

 while (!it.isLast()) it.moveNext()-›commit();

 // Всех выкинуть из стека, разрегистрировать.

 while (!m_locks.isEmpty()) m_locks.pop()-›unregObj();

}

// отмена всех объектов

void CTransaction::rollback() {

 // создали итератор

 ampIter‹CLockable› it(&(this-›m_locks));

 // пробежались по всем, отменились.

 it.goStart();

 while (!it.isLast()) it.moveNext()-›rollback();

 // Всех выкинуть из коллекции и разрегистрировать

 while (!m_locks.isEmpty()) m_locks.pop()-›unregObj();

}

// проверка, зарегистрирован ли объект.

int CTransaction::allready_locked(CLockable* _lc) {

 // создали итератор

 ampIter‹CLockable› it(&(this-›m_locks));

 it.goStart();

 while (!it.isLast()) if (it.moveNext() == _lc) return 1;

 return 0;

}

Шаг 29 - Единственный экземпляр класса - Одиночка или Singleton.

Как гарантировать единичность экземпляра некоего класса?