|
Программирование >> Обобщенные обратные вызовы
8. Обеспечение контейнеризации. Если программе нужен один объект обратного вызова для последующего использования, то, скорее всего, ей их понадобится несколько. А если у кого-то возникнет желание поместить такие объекты в контейнер, например, vector или list? Сейчас это невозможно, поскольку эти объекты нельзя присваивать - они не поддерживают оператор operateг=. Почему? Потому что они содержат ссылку, которая, будучи связана с объектом в конструкторе, в дальнейшем не может быть связана с каким-то другим объектом. Указатели не имеют такой привязанности к объектам и могут указывать на все, что вы только пожелаете. В рассматриваемом нами случае использование указателя вместо ссылки совершенно безопасно и не мешает компилятору сгенерировать конструктор по умолчанию и оператор копирующего присваивания. 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 Q { } template < class T > class callback: public callbackbase { public: typedef void (т::*Func)(); Связывание с реальным объектом callback(T& t, Func func) : object(&t), f(func) {} Выполнение функции обратного вызова void operator()() const { (object->*f)(); } private: Т* object; Func f; Теперь любой, кто захочет, может иметь list<callbackbase*> и полиморфно вызывать оператор operator() элементов этого списка. Конечно, использование 1 i st<shared ptr<cal 1 back> > еще предпочтительнее; см. [Suttert)2b]. Заметим, что добавление базового класса - компромисс, хотя и очень небольшой: мы вносим накладные расходы, связанные с дополнительной косвенностью, а именно - вызов виртуальной функции при запуске обратного вызова посредством базового интерфейса. Однако эти накладные расходы включаются в игру только при использовании базового интерфейса; кода, которому базовый интерфейс не требуется, эти расходы не касаются. > Рекомендация Рассмотрите возможность использования полиморфизма, с тем чтобы различные инстанцирования вашего шаблона класса могли использоваться взаимозаменяемо (если это имеет смысл для вашего шаблона класса). Если это так, то можно легко добиться полиморфизма, обеспечив наличие базового класса, совместно используемого всеми инстанцированиями шаблона класса. 10. (Идиома, компромисс). Можно обеспечить вспомогательную функцию таке саПЬаск для облегчения вывода типов. Сейчас пользователь должен явно указывать аргументы шаблона для временных объектов. 1ist< cal1back< widget > > 1; 1.push back( callback<widget>( w, Awidget::someFunc ) ); Ho зачем писать widget дважды? Неужели компилятору это и так непонятно? Да, непонятно, но вы можете помочь ему в контексте, когда нужны только временные объекты наподобие рассматриваемого. Вы можете разработать вспомогательную функцию. list< callback< widget > > 1; l.push back( make cal1 back ( w, &widget::SomeFunc ) ); Эта функция niake cal 1 back работает так же, как стандартная std: :make pai г. Это должна быть шаблонная функция, поскольку компилятор выводит типы только для этого вида шаблонов. Вот как должна выглядеть эта функция. tempiate<typename т > callback<T> make cal1back( т& t, void (T::*f) () ) { return callback<T>( t, f ); . (Компромисс). Добавление поддержки других сигнатур обратного вызова. Больше всего работы я оставил напоследок. Перефразируя, можно сказать: Есть много, друг Горацио, на свете сигнатур, которые и не снились нашей void (т::*F) ()! > Рекомендация Избегайте ограниченности ваших шаблонов; избегайте жесткого кодирования конкретных или менее общих типов. Если нам гарантированно достаточно только одной сигнатуры для функций обратного вызова, то не стоит преумножать сущности сверх необходимости и на этом можно остановиться. Но если мы хотим обеспечить возможность обратного вызова функций с разными сигнатурами, то нам придется существенно усложнить наш исходный текст. Я не хочу приводить здесь весь исходный текст, так как он достаточно скучный. (Если вы действительно хотите познакомиться с этим утомительно повторяющимся кодом, или если у вас бессонница - возьмите книгу [AlexandrescuOl], там вы найдете подобные примеры.) Я же только вкратце приведу несколько замечаний по этому поводу. Во-первых, следует подумать о константных функциях-членах. Простейший способ работы с ними - обеспечить параллельный обратный вызов, который использует соответствующую сопяг-сигиатуру, и в эгой версии не забывать получать и хранить т по ссылке или указателю на const. Во-вторых, надо вспомнить о различных возвращаемых типах. Простейший способ обеспечить варьируемый юзвращаемый тип - это добавить еще один параметр шаблона. В-третьих, функции обратного вызова могут иметь параметры. Вновь добавляем параметры шаблона, не забываем о наборе операторов operatorO с этими параметрами, а также посолить, поперчить и хорошенько перемешать это варево... Не забудьте добавить по новому шаблону для обработки каждого потенциального количества аргументов обратного вызова. Увы, рост кода приобретает просто взрывной характер, и вам надо подумать и вовремя остановиться, искусственно ограничив количество поддерживаемых параметров функций обратного вызова. Возможно, в будущем стандарте С++Ох мы и найдем что-то наподобие возможности работы с шаблонами с переменным числом параметров, но пока что это - пустые мечты. Резюме Обобщая все сказанное и добавив микроулучшения наподобие последовательного использования ключевого слова typename и соглашений об именовании, мы получим окончательную версию кода. class callbackBase { public: virtual void operatorO () const { }; vi rtual -callbackBaseO = 0; CallbackBase::-CallbackBaseO { } tempiate<typename T> class Callback : public CallbackBase { public: typedef void (t::*F)(); CallbackC T& t, F f ) : t (&t), f (f) { } void operatorO () const { (t ->*f )0 ; } private: T* t ; F f ; tempiate<typename т> callback<T> make callback( т& t, void (T::*f) () ) { return callback<T>( t, f );
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |