Идиомы и стили С++ [Albert Makhmutov] (fb2) читать постранично, страница - 3


 [Настройки текста]  [Cбросить фильтры]

и для параметризированного (то бишь для шаблона), и ровно один раз.

4. Кэширование.

Здесь сложнее. Об этом мне самому нужно почитать и полапать руками. Идея, как можно догадаться, в том, что если при обращении к умному указателю объект отсутствует в памяти, он считывается с диска. Проблемы самые очевидные в том, когда его снова отгружать на диск, разрушать объект, и как гарантировать единичность копии объекта при наличии многих ссылок.

Так. Пока тормозим. Интересно, о чем я напишу следующий шаг?

Шаг 4 - О двойной диспетчеризации.

Предположим, у нас есть массив, в котором мы храним карту местности. Разумеется, что элементы массива разнообразные - дома, колодцы, казино… ничего общего. Кроме суперкласса - предка естественно.

CBuilding

¦

______¦_______

¦ ¦ ¦

CHouse CWell CCasino

А карту эту мы отражаем разными способами. И даже не то, что разными способами, а имеем для такой благой цели несколько видов карт. Ну я не знаю, не картограф. Черви и пики. Нет, ладно. Радиоактивность и карма.

CMap

|

____________

| |

CRadioMap CCarmaMap

И что получается? Кто будет себя отрисовывать? И кто кого? Для каждой комбинации наследников CBuilding и CMap свой уникальный алгоритм. Что делать то будем? Какие феерические решения приходят… нет… не вам! Вашему коллеге или начальнику или подчиненному в голову? Да они ни сном ни духом о двойной диспетчеризации! Они скорее всего предложат получить информацию о типе во время исполнения, и запузырить в Ваш прекрасный проект кривоногий switch (){}. Да еще и положить в каждый класс статический член с информацией о типе… Одно звучание предыдущей фразы наводит на подозрения. Но что делаем мы? вот что:

class CBuilding: {

public:

 virtual void doDraw(CMap* map)=0;

}


class CHouse: public CBuilding {

public:

 virtual void doDraw (CMap* map) {

 // ВОТ ОНА САМАЯ КОРКА!

  map-›doDraw(*this);

 }

};


// Эти такие же.

class CWell: public CBuilding {

public:

 virtual void doDraw (CMap* map) {map-›doDraw(*this);}

};


class CCasino: public CBuilding {

public:

 virtual void doDraw (CMap* map) {map-›doDraw(*this);}

};


// Это абстрактный класс для карт.

class CMap {

public:

 virtual void doDraw (CHouse& cb)=0;

 virtual void doDraw (CWell& cb)=0;

 virtual void doDraw (CCasino& cb)=0;

};

Это конечно не все. Теперь нужно наследовать CRadioMap и CcarmaMap от общего предка CMap и в каждом классе рисовать реализацию алгоритма. За отрисовку отвечает карта, но какая масть - решает виртуальная CBuilding::doDraw(), а какое строение - выбирается перегруженная CMap::doDraw().

Одинаковое имя для функций отрисовки в разных классах давать не обязательно, но это является хорошим тоном при двойной диспетчеризации, и плохим без нее.

Круто? Это - подвиг неизвестного программиста. У Элджера был разобран пример со сложением чисел, очень красивый, но не сразу понятный. Там числа происходят от одного предка, что левый, что правый операнд оператора +, и по моему, обе диспетчеризации происходят по механизму виртуальных функций. Увы, мне лень набирать код.

Код к данному шагу я не проверял, в отличие от предыдущих. К диспетчеризации мы еще вернемся. Или не вернемся. Но следующий шаг однозначно про указатели.

Шаг 5 - Ведущие указатели (Master Pointers). Важные конструкторы.

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

1. Порождение ведущего указателя порождает объект, уничтожение ведущего указателя уничтожает объект;

2. Копирование ведущего указателя создает точную копию объекта;

3. Присваивание ведущего указателя уничтожает предыдущий объект и ставит на его место копию нового объекта.

Если же мы хотим получить однозначное соответствие объекта и его ведущего указателя, то нужно запретить создание объекта, кроме как при помощи ведущего указателя, и запретить создание ведущего указателя, кроме как специальной функцией. Последнее в общем не обязательно, а первое весьма важно.

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

class CThat {

private:

 int i;

public:

 CThat (int _i=0):i(_i) {}

 CThat (const CThat& _that):i(_that.i) {}

 CThat& operator=(const CThat& _that) {

  if (this == &_that) return *this;

  i = _that.i;

  return *this;

 }

};


class MasterPointer {

private:

 CThat* t;

public:

 // MasterPointer():t(new CThat){}

 MasterPointer(CThat _that=0):t(new CThat(_that)) {}

 MasterPointer(const MasterPointer& mp): t(new CThat((*mp.t))) {}

 ~MasterPointer() { delete t; }

 MasterPointer& operator=(const MasterPointer& mp) {

  if (this != &mp) {

   delete t;

   t = new CThat(*(mp.t));

  }

  return *this;

 }

};

Напоминать не надо, что this - это указатель на самого себя? Кстати и оказалось, что для реализации ведущего указателя класс указываемого объекта должен и сам иметь:

1. Конструктор