Чтение онлайн

ЖАНРЫ

Идиомы и стили С++

Makhmutov Albert

Шрифт:

Так же добавлю, что пользуюсь при подготовке Шагов компилятором BC3.1, а он поддерживает шаблоны не вполне так, как это делают современные компиляторы. То есть, если Вы просто скопируете код, вероятно он сразу даже не откомпилируется. Так что предупреждаю - если собираетесь пользоваться шаблонами - проверьте, что на эту тему думает компилятор (а так же насчет исключений и операторов вида xxxxxxx_cast‹›).

Мне же итератор нужен был исключительно для следующих Шагов, а совпадения фамилий, характеров и событий прошу считать случайными.

Шаг 27 - Умные указатели. Перегрузка operator*, operator,operator-›*.

Пробегая по верхам интересных идиом я упустил одну важную вещь. Поначалу она была не так важна, но пришло время замучать и ее. Я имею в виду то, что наши замечательно умные указатели, smart pointers, вообще-то имеют неполную семантику. То есть, они не полностью имитируют обычные, настоящие указатели. За примерами не надо ходить далеко - попробуем разыменовать смарт или вызвать функцию по указателю:

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: объекты, задействованные в транзакции, должны поддерживать специальный стандартный интерфейс, за который транзакция должна ими рулить. То есть, они либо должны быть порождены от специального абстрактного базового класса, либо они должны быть упакованы в специальный смарт-указатель - делающий то же самое.

Поделиться с друзьями: