Как стать автором
Обновить

Комментарии 45

Интересно оформлен код )
А мне тяжело читалось. В консоли код выглядит неплохо, потому как весь экран темный, а на белом фоне хабры — заставляет напрягать глаза.
Увы, слои тут использовать нельзя, если использовать тэг <code> то не получится ставить стрелки, подчеркивания, выделения и прочее :(.
В свое время после полугода работы на шарпе, как-то автоматически заложил в архитектуру приложения делегаты, будучи полностью уверенным что С++ их поддерживает… Это стоило мне 3-ех бессонных ночей… Цеплять Boost показалось излишним, поэтому пришел к подобному вашему коду.
Хорошая штука делегаты. Особенно их Qt реализация. Правда, в последнее время я все больше ползаю вокруг event bus — ИМХО, если правильно приготовить, дают очень хорошее архитектурное решение среднего слоя для связи компонентов программы.
У меня какая-то беда с вставлением картинок — в Internet Explorer отображаются пиксел-в-пексел, в chrome с увеличением ~130%. Что-то тут с DPI не то ©. Может тэгу <img> какой атрибут добавить?
Может быть в Chrome увеличение на 130% стоит? У меня все нормально, глянь в параметрах.
Dropbox — не лучший хостинг картинок для хабра.
А что не так? Сколько публикую — все отлично держит. Если будут проблемы с нагрузкой — переложу на хабрасторадж. Дропбокс удобен тем, что можно легко и быстро вносить правки в изображения для статей.
Дропбокс держит только если статью никто не читает. :)
Только что в этом убедился. Переложил на хабрасторадж :)
Писал когда-то свой, остановился в итоге на boost::signal, по крайней мере бардака с кол-вом параметров нет.

По данному коду могу сказать одно, что не стоит мешать соль с сахаром, а точнее не стоит в один делегат пихать методы с разными сигнатурами, это плохо кончится. Т.к. здесь работа с кол-вом параметров идет в рантайме вызвав делегат с неподходящими параметрами мы словим assert, но assert используется слегка для других целей. Думаю здесь было бы правильнее кидать исключение std::invalid_argument.

Я бы все же посоветовал строго типизировать и не играть с dynamic_cast, лишнее это. Строгая типизация даст ошибку на этапе компиляции, а данный код даст ошибку в рантайме и это очень плохо.
Реализация со строгой типизацией будет чуток потяжелее по коду. Как я уже писал, цель статьи — обрисовать как ЭТО вообще делается. А «промышленную» реализацию смотреть можно уже в тех же boost::signal. Проблема в том, что для неподготовленного читателя boost::signal — это ого-го какая шаблонная магия. А я, соответственно, постарался эту магию расписать на самых простых, вырожденных примерах.
Ну почему же, если поддержать ограниченное кол-во параметров, то думаю будет проще.
Да нет, можно конечно написать что-нибудь простое вида
Delegate2< void, int, int > delegate, но это не проиллюстрирует работу шаблонной магии и для практического применения такая реализация мало полезна, ИМХО.
В этом плане согласен, магии нет. Магия внутри boost::mpl )
Тоже хорошая реализация, но там префикс используется по количеству агрументов плюс нужно вручую прототип прописывать. Ну и код, понятное дело, сложнее. ИМХО, буст — наше все :).
Автор пишет, что в JavaScript есть делегаты. Это не совсем верно, так как делегат подразумевает четкую типизацию возвращаемого значения и аргументов. Конечно же, в JavaScript это реализовать типизацию аргументов и возвращаемых значений невозможно. Однако, функции являются одним из типов данных, поэтому их можно, например, передавать в другие функции как обычные переменные. Так что механизм делегатов можно повторить, но это не будет делегатами.
делегат подразумевает четкую типизацию возвращаемого значения и аргументов

Википедия с вами не согласится :). В общем случае объект типа «делегат» и объект типа «first-class function object» очень похожи. Разница в деталях — например, делегат может быть связан с более чем одной функцией. Или быть асинхронным. Определение с википедии:

«Programming languages in general do not support delegation as a language concept, but there are a few exceptions, most notably ECMAScript»
Определение с википедии :)
A delegate is a form of type-safe function pointer
… «used by the .NET Framework»? То, что в C# «делегат» — это термин для обозначения функциональности самого языка не отменяет то, что в более широком смысле делегат обозначает объектно-ориентированный указатель на функцию. В любом случае, ИМХО, какая разница как назвать?
Возможно я придираюсь конечно, но с моей точки зрения, делегаты — это, действительно, понятие, введенное с приходом языка С#, но задолго до этого они существовали во многих функциональных языках, к которым JavaScropt тоже можно отнести
Спасибо, мощно
Стараюсь. Может, когда-нибудь, смогу так же рассказать про реализацию event bus O_O.
>А в C++ делегатов нет — издержки компилируемого языка.

Это неверно. Компилируемость (или иная стратегия трансляции языка) не влияет на наличие или отсутствие делегатов.
Соглашусь, это меня что-то переклинило. Заменил странное утверждение на странный анимэшный смайлик О_О.
> Как должен выглядеть делегат на C++?

Вот так:

Victim test_class;
Delegate test_delegate;

test_delegate.Connect([&test_class] { test_class.Foo(); });


Забудьте вы уже этот С++2003, используйте C++0x! ;)
ИМХО, рановато еще. Вот примут финальную версию стандарта, будет поддержка из коробки (а не спецключами компилятора) в основных компиляторах и IDE — тогда самое то.

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

test_delegate.Connect([&test_class] { test_class.Foo(10, 20, 30, 40); });

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

Помещение самой обработки внутрь лямбд — это то, что активно использует Java. Субъективно кода получается больше, чем при использовании делегатов.
Конечно расширяется, кто спорит то? Потом вы его расширите на функциональные объекты (с перегруженным оператором (), в том числе другие делегаты), потом расширите на биндинг параметров, потом сделаете имитацию лямбд с помощью операторов и placeholder'ов, и получитие boost.function + boost.bind + boost.lambda. Какой смысл, если нормальные лямбды уже на подхоже, и даже уже есть в msvs и gcc?
Уже пора как минимум в отношении того, что 2010 микрософтовский поддерживает из коробки (и без ключей) — лямбды, auto, rvalue-ссылки и std::function с shared_ptr. Они уже не изменятся.
Удивился, что в статье не упомянуты указатели на методы типа double (Foo::*funcPtr)( long ) = &Foo::One;
Мне не сложно, но зачем их упоминать? O_O. Это же базовый синтаксис языка, он в любом учебнике описан. Какой интерес про него в статье писать? :)
Чтобы рассказать, что делегаты лучше указателей из-за типизации. Это не такая уж очевидная вещь.
У меня есть «Как правило, делегат очень похож на „указатель на функцию“ в C++, с той основной разницей что делегат может указывать на метод произвольного объекта.» :). Этого должно хватить.
Спасибо за статью!
В принципе всё более менее понятно за исключением одного момента в листинга номер 5(считать сверху вниз)

public:
Container( T* c, M m): m_class( c ), m_method( m ) {}
private:
T* m_class; M m_method;
};


Что за магия с объявлением конструктора?
Смущает двоеточие после скобки и эти интересные конструкции типа m_class©
еще кажется там точки с запятой не хватает после } или это тоже так задумано?(тогда я вообще ничего не понимаю)
С новым годом! Все по плану, так по стандарту C++ вызываются конструкторы полей (fields) класса. Рад, что мой фунаментальный труд до сих пор приносит пользу :). BTW, в C++11 ссоздание делегатов теперь попроще — там есть variadic templates и не нужно делать отдельный шаблон под каждое количество аргументов.
С Новым Годом!
Статья актуальна до тех пор, пока есть люди(как я) которые начинают разбираться в шаблонах С++.
В общем то я еще вчера хорошо подумал, погуглил и понял, что мой предыдущий вопрос это список инициализации конструктора и ничего хитрого там нет…
Но я очень рад, что есть возможность пообщаться с автором, потому что следующий вопрос получился более специфичным и нагуглить его не так просто, как предыдущий…

Не могу толком понять механизм извлечения типа из сигнатуры(это последний листинг из тех что в виде картинки).
Сигнатуру я понимаю так(может быть я и тут ошибаюсь): допустим есть метод void method(int param) его сигнатура это void(int)
Следовательно все что нам нужно — это извлечь тип аргумента из сигнатуры. Для этого необходимо воспользоваться трюком с частичной специализацией шаблонов


// Специализация для метода с одним аргументом.
template< class T, class A1 >
class Container< T, void (T::*)(A1) >: public IContainer
{
typedef void (T::*M)(A1);
typedef Arguments A;

особое непонимание вызывает конструкция void (T::*)(A1) которая вроде как описывает указатель на функцию возвращающую тип void… по всей виимости T::* означает что эта функция — член(метод) класса T c параметром A1? Хотя именно запись T::* мне не понятна… Указатель из пространства имен T?
Тут требуется подсказка…

И дальше хитрые typedef… особенно первый. Зачем он? На первый взгляд кажется, что после него void будет эквивалентен (T::*M)(A1)...?
второй как-то более привычно выглядит и кажется делая алиас для типа Arguments, называя его просто A для удобства.

А за предыдущий вопрос прошу прощения, он, конечно же, не стоил вашего времени, потраченного на ответ.
Механизм получения типа из сигнатуры подробно раписан в моей сателлитной статье, там как раз ссылка есть перед этим страшным листингом. T-четыре-точки-зведочка это указатель на метод класса T, эволюция там примерно такая:

void (*)(int) // указатель на функцию
void (Myclass::*)(int) // указатель на метод класса Myclass
void (T::*)(A1) // указатель на метод произвольного класса T с типом аргумента A1


typedef — это всего лишь сокращение кода, чтобы вместо вот этого длинного типа писать короткое «M»:

void (Myclass::* foo)(int) = 0; // без typedef
typedef void (Myclass::* M)(int);
M foo = 0; // с typedef
Большое спасибо!
Благодаря вашим подсказкам теперь наконец полностью разобрался в вашем коде и его работе.

Долго не мог понять, как именно M в записи typedef void (T::*M)(A1); будет именем(алиасом) типа.
Обычно привык видеть что-то вроде typedef int my_int; т.е. ожидал пробел между типом и алиасом.
То, что предлагает Григорий — лучшее из всего, что есть на «рынке»!
Это аналог инициализации, видите ли если в классе могут быть атрибуты ввиде ссылок или с пометкой const. Синтаксис С++ запрещает создание неинициализированой ссылки или модификацию константы, то есть нужна инициализации, а в теле конструктора можно только присвоить, тут то и приходит на помощь такой синтаксис.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации