Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 [ 69 ] 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

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

Присваивание члену object

explicit call back(т& t) : object(t) {}

To, что мы обнаружили в рассматриваемой строке, - вопрос стилистики, который сам по себе не является вопросом дизайна, но заслуживает отдельной рекомендации.

> Рекомендация

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

(Мелочь). Комментарий не верен. Слово "присваивание" в комментарии не верно и вводит в заблуждение. Более точно в конструкторе мы "привязываем" ссылку к объекту типа т. Словом, будет лучще, если комментарий будет таким, как показано ниже

привязка к реальному объекту explicit call back(т& t) : object(t) {}

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

explicit callback(T& t) : object(t) {}

5. Функция execute должна быть const. В конце концов, функция execute никак не влияет на состояние объекта cal 1 Ьаск<т>! Это возвращает нас к азам - рекомендация о корректном использовании const, как вино, с возрастом становится только лучше. Значение корректного использования const известно в С и С++ как минимум с начала 1980-х годов, и это значение никуда не делось и в новом тысячелетии, и на него не влияет массовое использование шаблонов.

запуск функции обратного вызова void executeO const {(object.*F)();}

> Рекомендация

He забывайте о корректном использовании const.

Теперь, когда мы разобрались с функцией execute, перейдем к более серьезной идиоматической проблеме.

6. (Идиома). Функция execute должна быть оператором operatorO. В С++ использование оператора вызова функции для выполнения операций в стиле функции идиоматично. Кстати, тогда комментарий, и так несколько излишний, становится совершенно ненужным и может быть удален. Код теперь идиоматически комментирует сам себя.

void operator О () const { (object.*F)(); }

"Но, - можете удивиться вы, -- если мы обеспечиваем оператор вызова функции, значит, перед нами разновидность функционального объекта?" Отличный вопрос, который приводит нас к наблюдению, что обратный вызов также можно рассматривать как функциональный объект.

> Рекомендация

Идиоматические функциональные объекты следует обеспечивать оператором operatorO вместо именованной функции вызова.



Ловушка: (Идиома). Должен ли данный обратный вызов быть производным от std: :ипагу function? См. раздел 36 в [MeyersOI], где более подробно изложено обсуждение адаптируемости и почему в общем случае это Хорошая Вещь. Увы, в данном примере имеются две отличные причины не порождать обратный вызов от std: :unary function, как минимум, не сейчас.

• Это не унарная функция. У нее нет параметров, в то время, как у унарной функции - один параметр (voi d не в счет!).

• Порождение от std: :unary function, в любом случае, не улучшает расширяемость. Позже мы увидим, что обратный вызов должен работать и с другими видами сигнатур функций, и в зависимости от количества параметров может не оказаться соответствующего стандартного базового класса. Например, если мы поддерживаем функции обратного вызова с тремя параметрами, то стандартного класса std:: ternary function, от которого мы могли бы породить собственный класс, не существует.

Порождение от std::unary function или std::binary function - удобный способ снабдить обратный вызов небольшим количеством важных определений typedef, которые могут использоваться различными профаммными средствами, такими как замыкания, но это имеет значение, только если вы используете их как настоящие функциональные объекты. Вряд ли это окажется необходимо в силу природы обратного вызова и его предназначения. (Если в будущем дело обернется так, что обратные вызовы должны будут использоваться таким образом с одним или двумя параметрами, то соответствующие версии можно будет породить от std: :unary f uncti on и std::binary function.)

Исправление механических ошибок и ограничений

б) механические ограничения полезности данного средства

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

template < class Т > class callback { public:

typedef void (т::*Func)();

Связывание с реальным объектом call back(т& t, Func func) : object(t), f(func) {} Выполнение функции обратного вызова void operator()() const { (object.*f)(); } private:

T& object; Func f;

Теперь используемая функция может изменяться в процессе выполнения программы; к коду легко добавить функцию-член, которая позволит пользователю изменить функцию, с которой связан существующий объект cal 1 back, что было невозможно в предыдущей версии кода.

> Рекомендация

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



8. Обеспечение контейнеризации. Если программе нужен один объект обратного вызова для последующего использования, то, скорее всего, ей их понадобится несколько. А если у кого-то возникнет желание поместить такие объекты в контейнер, например, vector или list? Сейчас это невозможно, поскольку эти объекты нельзя присваивать - они не поддерживают оператор operator=. Почему? Потому что они содержат ссылку, которая, будучи связана с объектом в конструкторе, в дальнейшем не может быть связана с каким-то другим объектом.

Указатели не имеют такой "привязанности" к объектам и могут указывать на все, что вы только пожелаете. В рассматриваемом нами случае использование указателя вместо ссылки соверщенно безопасно и не мешает компилятору сгенерировать конструктор по умолчанию и оператор копирующего присваивания.

template < class т > class cal1 back { public:

typedef void (T::*Func)() ;

Связывание с реальным объектом

callback(T& t, Func func) : objectC&t), f(func) {}

выполнение функции обратного вызова

void operator()() const { (object->*f)(); } private:

T* object;

Func f;

Теперь можно написать, например,

list< callback< widget > > lcw(w, &widget::SomeFunc);

> Рекомендация

Будет гораздо лучше, если разрабатываемые вами объекты будут совместимы с контейнерами. В частности, чтобы быть помещенным в стандартный контейнер, объект должен поддерживать присваивание.

"Минутку, - можете удивиться вы, - если у меня может быть список такого вида, то почему я не могу иметь список объектов обратного вызова произвольных типов, чтобы я мог запомнить их все и вызывать по мере необходимости?" Почему бы и нет, если вы добавите базовый класс.

9. Разрешение полиморфизма: обеспечение общего базового класса для объектов обратного вызова. Если мы хотим позволить пользователю иметь list<callbackbase*> (или лучше 1 i st<shared ptr<cal 1 backbase> >), то мы можем сделать это, просто обеспечив наличие базового класса, который по умолчанию ничего не делает в своем операторе operatorO.

class callbackbase { public:

vi rtual void operator()() const { }; vi rtual -callbackbase() = 0;

callbackbase::~callbackbase() { }

template < class T >

class callback: public callbackbase {

public:

typedef void (т::*Func)();

Связывание с реальным объектом

callback(T& t, Func func) : objectC&t), f(func) {}

Выполнение функции обратного вызова

void operator()() const { (object->*f)(); }



0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 [ 69 ] 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85