obj = *(smart_ptr);
(obj-›*ptr_to_funct) (some_parameter);
С первой проблемой рассчитаться легко. Если Вы НЕ читаете сейчас этот Шаг, не беспокойтесь - решение придет само, в тот момент, когда задача возникнет.
//Ясно, это реализации перегруженных операторов-селекторов.
CSmth* operator-›() const { return prt_real; }
CSmth& operator* () const { return *ptr_real; }
operator CSmth* () const { return ptr_real; }
А что вторая проблема? Да, тут ситуация намного серьезнее, и если Вы опять-таки не читаете этот Шаг, то нужно немедленно прочесть его - или первоисточник - статью Мейерса в Dr. Dobb's Journal. Только там придется продираться через тучные стада шаблонов и долгих рассуждений. Без шаблона конечно не обойтись, но нужно ухватить хотя бы идею. Поэтому сделаем так, как нормальный человек читает детективы Марининой: первые и последние две страницы.
Сначала, кто такой 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()добавляет объект к транзакции, то есть распространяет свое действие на него, тем самым блокируя изменения со стороны других клиентов. После использования объекты автоматически разрегистрируются.
Читать дальше