Комментарии 45
Интересно оформлен код )
+9
А мне тяжело читалось. В консоли код выглядит неплохо, потому как весь экран темный, а на белом фоне хабры — заставляет напрягать глаза.
0
Увы, слои тут использовать нельзя, если использовать тэг <code> то не получится ставить стрелки, подчеркивания, выделения и прочее :(.
0
В свое время после полугода работы на шарпе, как-то автоматически заложил в архитектуру приложения делегаты, будучи полностью уверенным что С++ их поддерживает… Это стоило мне 3-ех бессонных ночей… Цеплять Boost показалось излишним, поэтому пришел к подобному вашему коду.
0
У меня какая-то беда с вставлением картинок — в Internet Explorer отображаются пиксел-в-пексел, в chrome с увеличением ~130%. Что-то тут с DPI не то ©. Может тэгу <img> какой атрибут добавить?
0
Dropbox — не лучший хостинг картинок для хабра.
+2
Писал когда-то свой, остановился в итоге на boost::signal, по крайней мере бардака с кол-вом параметров нет.
По данному коду могу сказать одно, что не стоит мешать соль с сахаром, а точнее не стоит в один делегат пихать методы с разными сигнатурами, это плохо кончится. Т.к. здесь работа с кол-вом параметров идет в рантайме вызвав делегат с неподходящими параметрами мы словим assert, но assert используется слегка для других целей. Думаю здесь было бы правильнее кидать исключение std::invalid_argument.
Я бы все же посоветовал строго типизировать и не играть с dynamic_cast, лишнее это. Строгая типизация даст ошибку на этапе компиляции, а данный код даст ошибку в рантайме и это очень плохо.
По данному коду могу сказать одно, что не стоит мешать соль с сахаром, а точнее не стоит в один делегат пихать методы с разными сигнатурами, это плохо кончится. Т.к. здесь работа с кол-вом параметров идет в рантайме вызвав делегат с неподходящими параметрами мы словим assert, но assert используется слегка для других целей. Думаю здесь было бы правильнее кидать исключение std::invalid_argument.
Я бы все же посоветовал строго типизировать и не играть с dynamic_cast, лишнее это. Строгая типизация даст ошибку на этапе компиляции, а данный код даст ошибку в рантайме и это очень плохо.
+2
Реализация со строгой типизацией будет чуток потяжелее по коду. Как я уже писал, цель статьи — обрисовать как ЭТО вообще делается. А «промышленную» реализацию смотреть можно уже в тех же boost::signal. Проблема в том, что для неподготовленного читателя boost::signal — это ого-го какая шаблонная магия. А я, соответственно, постарался эту магию расписать на самых простых, вырожденных примерах.
+2
А мне понравились вот эти www.rsdn.ru/article/cpp/delegates.xml. Очень удобно и легковесно.
0
Автор пишет, что в JavaScript есть делегаты. Это не совсем верно, так как делегат подразумевает четкую типизацию возвращаемого значения и аргументов. Конечно же, в JavaScript это реализовать типизацию аргументов и возвращаемых значений невозможно. Однако, функции являются одним из типов данных, поэтому их можно, например, передавать в другие функции как обычные переменные. Так что механизм делегатов можно повторить, но это не будет делегатами.
0
делегат подразумевает четкую типизацию возвращаемого значения и аргументов
Википедия с вами не согласится :). В общем случае объект типа «делегат» и объект типа «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»
+1
Определение с википедии :)
A delegate is a form of type-safe function pointer
0
… «used by the .NET Framework»? То, что в C# «делегат» — это термин для обозначения функциональности самого языка не отменяет то, что в более широком смысле делегат обозначает объектно-ориентированный указатель на функцию. В любом случае, ИМХО, какая разница как назвать?
0
Спасибо, мощно
+1
>А в C++ делегатов нет — издержки компилируемого языка.
Это неверно. Компилируемость (или иная стратегия трансляции языка) не влияет на наличие или отсутствие делегатов.
Это неверно. Компилируемость (или иная стратегия трансляции языка) не влияет на наличие или отсутствие делегатов.
+2
> Как должен выглядеть делегат на C++?
Вот так:
Забудьте вы уже этот С++2003, используйте C++0x! ;)
Вот так:
Victim test_class; Delegate test_delegate; test_delegate.Connect([&test_class] { test_class.Foo(); });
Забудьте вы уже этот С++2003, используйте C++0x! ;)
+1
ИМХО, рановато еще. Вот примут финальную версию стандарта, будет поддержка из коробки (а не спецключами компилятора) в основных компиляторах и IDE — тогда самое то.
BTW, написанное замыкание по коду почти столько же места занимает. В чем профит?
BTW, написанное замыкание по коду почти столько же места занимает. В чем профит?
0
Профит в том, что делегатом может быть любая функция без параметров, а не только функция-член какого-то класса. Ну или например функция/функция-член, которой передаются какие-то дополнительные параметры:
test_delegate.Connect([&test_class] { test_class.Foo(10, 20, 30, 40); });
Да и вообще, можно саму обработку кода прям в внутрь лямбды поместить.
test_delegate.Connect([&test_class] { test_class.Foo(10, 20, 30, 40); });
Да и вообще, можно саму обработку кода прям в внутрь лямбды поместить.
0
Приведенный мною код довольно легко расширяется для поддержки функций, не являющихся членами класса. Достаточно специализировать Delegate для void (*)(что-то-там), спрятав тип класса.
Помещение самой обработки внутрь лямбд — это то, что активно использует Java. Субъективно кода получается больше, чем при использовании делегатов.
Помещение самой обработки внутрь лямбд — это то, что активно использует Java. Субъективно кода получается больше, чем при использовании делегатов.
0
Конечно расширяется, кто спорит то? Потом вы его расширите на функциональные объекты (с перегруженным оператором (), в том числе другие делегаты), потом расширите на биндинг параметров, потом сделаете имитацию лямбд с помощью операторов и placeholder'ов, и получитие boost.function + boost.bind + boost.lambda. Какой смысл, если нормальные лямбды уже на подхоже, и даже уже есть в msvs и gcc?
0
Уже пора как минимум в отношении того, что 2010 микрософтовский поддерживает из коробки (и без ключей) — лямбды, auto, rvalue-ссылки и std::function с shared_ptr. Они уже не изменятся.
+1
Удивился, что в статье не упомянуты указатели на методы типа double (Foo::*funcPtr)( long ) = &Foo::One;
0
Мне не сложно, но зачем их упоминать? O_O. Это же базовый синтаксис языка, он в любом учебнике описан. Какой интерес про него в статье писать? :)
0
Спасибо за статью!
В принципе всё более менее понятно за исключением одного момента в листинга номер 5(считать сверху вниз)
Что за магия с объявлением конструктора?
Смущает двоеточие после скобки и эти интересные конструкции типа m_class©
еще кажется там точки с запятой не хватает после } или это тоже так задумано?(тогда я вообще ничего не понимаю)
В принципе всё более менее понятно за исключением одного момента в листинга номер 5(считать сверху вниз)
…
public:
Container( T* c, M m): m_class( c ), m_method( m ) {}
private:
T* m_class; M m_method;
};
Что за магия с объявлением конструктора?
Смущает двоеточие после скобки и эти интересные конструкции типа m_class©
еще кажется там точки с запятой не хватает после } или это тоже так задумано?(тогда я вообще ничего не понимаю)
0
С новым годом! Все по плану, так по стандарту C++ вызываются конструкторы полей (fields) класса. Рад, что мой фунаментальный труд до сих пор приносит пользу :). BTW, в C++11 ссоздание делегатов теперь попроще — там есть variadic templates и не нужно делать отдельный шаблон под каждое количество аргументов.
0
С Новым Годом!
Статья актуальна до тех пор, пока есть люди(как я) которые начинают разбираться в шаблонах С++.
В общем то я еще вчера хорошо подумал, погуглил и понял, что мой предыдущий вопрос это список инициализации конструктора и ничего хитрого там нет…
Но я очень рад, что есть возможность пообщаться с автором, потому что следующий вопрос получился более специфичным и нагуглить его не так просто, как предыдущий…
Не могу толком понять механизм извлечения типа из сигнатуры(это последний листинг из тех что в виде картинки).
Сигнатуру я понимаю так(может быть я и тут ошибаюсь): допустим есть метод 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 для удобства.
А за предыдущий вопрос прошу прощения, он, конечно же, не стоил вашего времени, потраченного на ответ.
Статья актуальна до тех пор, пока есть люди(как я) которые начинают разбираться в шаблонах С++.
В общем то я еще вчера хорошо подумал, погуглил и понял, что мой предыдущий вопрос это список инициализации конструктора и ничего хитрого там нет…
Но я очень рад, что есть возможность пообщаться с автором, потому что следующий вопрос получился более специфичным и нагуглить его не так просто, как предыдущий…
Не могу толком понять механизм извлечения типа из сигнатуры(это последний листинг из тех что в виде картинки).
Сигнатуру я понимаю так(может быть я и тут ошибаюсь): допустим есть метод 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 для удобства.
А за предыдущий вопрос прошу прощения, он, конечно же, не стоил вашего времени, потраченного на ответ.
0
Механизм получения типа из сигнатуры подробно раписан в моей сателлитной статье, там как раз ссылка есть перед этим страшным листингом. T-четыре-точки-зведочка это указатель на метод класса T, эволюция там примерно такая:
typedef — это всего лишь сокращение кода, чтобы вместо вот этого длинного типа писать короткое «M»:
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
0
Большое спасибо!
Благодаря вашим подсказкам теперь наконец полностью разобрался в вашем коде и его работе.
Долго не мог понять, как именно M в записи typedef void (T::*M)(A1); будет именем(алиасом) типа.
Обычно привык видеть что-то вроде typedef int my_int; т.е. ожидал пробел между типом и алиасом.
Благодаря вашим подсказкам теперь наконец полностью разобрался в вашем коде и его работе.
Долго не мог понять, как именно M в записи typedef void (T::*M)(A1); будет именем(алиасом) типа.
Обычно привык видеть что-то вроде typedef int my_int; т.е. ожидал пробел между типом и алиасом.
0
То, что предлагает Григорий — лучшее из всего, что есть на «рынке»!
+1
Это аналог инициализации, видите ли если в классе могут быть атрибуты ввиде ссылок или с пометкой const. Синтаксис С++ запрещает создание неинициализированой ссылки или модификацию константы, то есть нужна инициализации, а в теле конструктора можно только присвоить, тут то и приходит на помощь такой синтаксис.
0
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Простейший делегат на C++