Я разместил реализацию и краткий тест / демо здесь:
http://ideone.com/CNJDi
Я придумал подход к C ++ Delegates, который сильно отличается от других вещей, которые я видел. В частности, проблема, которую я хочу решить, состоит в том, чтобы иметь унифицированное решение с указателем на функцию, в котором данный экземпляр может быть либо a) членом fn ptr + object ptr, либо b) некомбинированным fn ptr. Как только вы инициализируете делегат и передаете его другому коду, он может просто вызвать его динамически, даже не зная, к какому типу объекта он пришел (обычно указатели на функции-члены могут вызываться только путем статической ссылки на класс, из которого он получен. Boo !). Теоретически это легко возможно, но C ++ усложняет выполнение чего-либо подобного. Поэтому я исказил язык, чтобы он заработал.
Основным недостатком моей реализации является то, что она технически «не соответствует стандартам» из-за недопустимых приведений / вызовов указателей на функции-члены (по крайней мере, я на 95% уверен, что стандарт не защищает меня здесь …). Но это делает случалось работать на компиляторах, на которых я его тестировал (включая Visual Studio 2012). Я также думаю, что ясно, что это «сработает», если функции-члены будут реализованы простым способом компилятором.
Я посмотрел на некоторые другие реализации, но мне они показались такими сложными и неуклюжими в использовании. Некоторые даже полагались на инструменты сборки, чтобы генерировать функции-заглушки для вызова функций-членов, тогда как мой использует только макросы и шаблоны. Я думаю, что отсутствие надлежащих делегатов является основным недостатком C ++, но я считаю, что «меньше ненавижу этот обходной путь». Теперь мне нужно решить, хочу ли я на самом деле его использовать или мне это нравится только потому, что я об этом подумал.
Вот как вы используете это:
А. Объявить тип делегата
typedef DELEGATE(float, ARGS(int, int)) Delegate1;
Этот макрос автоматически объявляет статические типы и указатели на функции-члены, поэтому вам не нужно вводить сигнатуру дважды. Расширяется до Delegate<float (*)(int, int), float (Null::*)(int, int)>
, Компилятор использует любой из них, подходящий в зависимости от того, как экземпляр делегата был инициализирован для выполнения вызовов позже. И компилятор использует его для статической проверки аргументов, предоставляемых вызовами кодера. Макрос ARGS является чистым синтаксическим сахаром для отделения от возвращаемого типа: DELEGATE (float, int, int) — то же самое.
Б. Инициализировать:
Delegate1 d = Delegate1(test1); // static function
Delegate1 e = Delegate1((Delegate1::MemberType)&TestClass::test2, &obj); // member
эти статические функции и функции-члены теперь хранятся как один и тот же тип! obj должен быть действительным указателем на экземпляр TestClass, и TestClass :: test2 лучше вернуть float и принять (int, int) в качестве аргументов, как описано выше. Это основная ошибка при использовании, компилятор не может поймать ошибки, допущенные здесь.
C. вызвать: INVOKE(d, ARGS(5, 6))
(возвращает значение с плавающей точкой в этом примере)
Несмотря на внешний вид, этот список аргументов так же безопасен, как и любой вызов функции C ++! Он проверяет аргументы, используя вышеупомянутую подпись float (*) (int, int), указанную выше! Он может поддерживать любое количество аргументов, он просто должен соответствовать подписи. Вы получаете дружественную ошибку компилятора, если добавляете слишком много / слишком мало аргументов, используете неверные типы аргументов и т. Д. Из компилятора. Опять же, ARGS является синтаксическим сахаром, INVOKE(d, 5, 6)
та же.
Но если использовать INVOKE в компиляторе, который не одобряет, это может привести к сбою программы 🙁
У меня есть несколько вопросов:
Почему бы просто не использовать std :: bind, доступный в C ++ 11 / Boost? С ним могут использоваться функции-члены, указатели функций, лямбда-функции, функторы и любые другие вызываемые объекты. Я лично предпочитаю избегать препроцессора и макросов любой ценой; это может сделать отладку гигантского беспорядка. Скотт Мейерс (Скотт Мейерс) (автор Effective C ++, More Effective C ++ и Effective STL) также не рекомендует использовать препроцессор.
Ссылка для привязки: http://en.cppreference.com/w/cpp/utility/functional/bind
Я бы также рассмотрел использование std :: function для инкапсуляции различных типов, но bind может обрабатывать почти все чисто. http://en.cppreference.com/w/cpp/utility/functional/function
Может кто-нибудь найти хорошие компиляторы, на которых мой пример реализации не работает правильно?
На мой взгляд, это не имеет значения, если код не соответствует стандартам в первую очередь. Если вам нужно странным образом взглянуть на правила языка, ваш код наверное не соответствует стандартам.
Соответствующий стандартам код важен, потому что, хотя детали реализации компилятора могут измениться, наблюдаемое поведение результирующего двоичного файла не может. Ошибки, которые являются проблемой соответствия стандартам, более вероятно, будут исправлены, чем ошибки, которые являются проблемой неработающего кода, не соответствующего стандартам.
Я надеюсь, что это либо работает полностью, либо компилятор / программа отключается при первом использовании. Но есть ли вероятность того, что какое-то время это сработает, а затем произойдет случайный сбой?
Если ваш код вызывает неопределенное поведение, тогда он будет работать нормально, а в худшем случае провальный сбой — это стандартная возможность.
Это кажется простым / чистым в использовании по вашему мнению? Или вы находите другие реализации проще? Какие? Можете ли вы придумать хороший способ
улучшить мой синтаксис / удобство использования моего как-нибудь?
Честно говоря, я думаю, что это беспорядок. Чтобы использовать ваших делегатов, мне нужно использовать три разных макроса, отдельный typedef, который, по-видимому, не является обязательным, и приведение в стиле C. Честно говоря, это больно использовать и смотреть.
Вы когда-нибудь использовали бы это? Или я должен просто укусить пулю и использовать
более безопасное, но более сложное альтернативное решение?
Опять же, если честно, я бы просто использовал std::function
и друзья (или boost::function
если у вас нет компилятора C ++ 11). Даже если вы найдете std::function
более сложный, безусловно, более чистый, стандартный и способный.