Чисто техническая статья. Рассматривается тема, которая заявлена в заголовке, плюс разные практические методы, которые в этом будут полезны.
Тему предваряет обзор материалов, которые я использовал при написании своей статьи, в одном из которых есть сравнение C# делегатов с техникой которая заменяет их использование на Java, которое я тоже собираюсь проанализировать в конце.
Обзор материалов, посвященных делегатам и тому что с ними связано
Удивительную статью я обнаружил в истории Хабра называется: Как должен выглядеть делегат на C++ аж 2011 года! Там действительно реализован делегат на С++. Код компилируется и работает — генерирует консольный вывод. Делегат, конечно, получился специфический, но обо всем по порядку. Статья начинается с утверждения что:
В C# есть делегаты. В python есть делегаты. В javascript есть делегаты.В Java есть выполняющую их роль замыкания. А в C++ делегатов нет O_O
Так начинается аннотация к статье Как должен выглядеть делегат на C++.
Мне стало интересно это я что-то не понимаю или автор маленько слукавил чтобы представить Хабру свою, безусловно замечательную (по-моему) работу, по которой вполне можно изучать применение темплейтов С++. Всем, кто хочет поупражняться с шаблонами (templates) на C++ я настоятельно рекомендую попробовать воспроизвести пример из статьи Как должен выглядеть делегат на C++ и разобраться как он работает.
Такой пример я бы рекомендовал для изучения в ВУЗах чтобы закрепить понимание указателей на функции, темплейтов и чтобы отбить охоту у студентов применять С++ темплейты там где они не приносят особой пользы.
Теперь посмотрим работу иностранного специалиста. Jon Skeet в своей заметке (или статье) The Beauty of Closures говорит что в Java нет делегатов и поэтому для представления объекта-предиката (объекта-операции для проверки условия) он предлагает использовать интерфейс декларирующий единственную функцию. В этом смысле интересно отметить, что Jon Skeet фактически уравнял понятие делегата с понятием интерфейса с единственным методом. Цитата:
In C# the natural way of representing a predicate is as a delegate, and indeed .NET 2.0 contains a
Predicate<T>
type. (...) In Java there's no such thing as a delegate, so we'll use an interface with a single method.
По крайней мере он вполне успешно, по-моему, сравнивает реализацию замыканий реализованную через делегат на С#, с реализацией замыканий реализованных через интерфейс на Java в своей статье. Мы еще вернемся к сравнению делегата с интерфейсом с единственным методом и разберем подробно насколько это корректное сравнение, после того как разберем гипотетическую реализацию делегата на С++.
Чтобы немного разбавить через чур серьезную тему, а также в качестве справки по личности Джона Скита я приведу ссылку на другую статью 18 фактов о Джоне Ските на Хабре, которая говорит нам о том, что анекдоты про известных людей вроде Чапаева и Штирлица не только наша национальная традиция. Я, кстати сказать, даже не знал о существовании такого специалиста пока не взялся анализировать материалы на тему делегатов, замыканий.
Определения делегатов
Теперь посмотрим на что фокусируется внимание в определениях делегатов. Все в той же статье у Джона Скита мы найдем следующее отличительную характеристику делегатов:
Цитата из статьи-перевода все того же Джона Скита на Хабре Делегаты и события в .NET:
Делегаты обладают и другой функциональностью, но возможность делать вызовы методов с заранее определёнными сигнатурами — это и есть самая суть делегатов.
Компания разработчик C# определяет нам другую характеристику делегатов по ссылке Delegates (C# Programming Guide):
Делегаты используются чтобы передавать методы как аргументы в другие методы
Ну и я попробую сформулировать то, что конкретно мне кажется важным для понимания делегатов, исходя из моего собственного опыта: делегатом называют и тип переменных, и сами переменные которые используются в коде для хранения/передачи методов (именованных или анонимных блоков кода) для отложенных вызов этих методов через эти переменные. Для примера:
C# делегат как тип определяется в виде, например:
public delegate bool Predicate<T>(T obj);
и переменную типа Predicate<T> которая определяет параметр predicate при объявлении функции в виде:
public static IList<T> Filter<T>(IList<T> source, Predicate<T> predicate)
мы тоже называем делегатом. С точки зрения человеческого языка все логично, если тип мы назвали делегатом, то переменную этого типа мы именуем именем типа.
Если мы не имеем в виду, конкретное значение, которое хранится в переменной нам достаточно упоминания типа этого значения. Очевидно, что в переменной типа Делегат будет храниться функция, но, если мы будем именовать такие переменные Функциями у нас будет гораздо больше путаницы чем та, которая возникает из-за неоднозначности относительно того, что имеется в виду, когда мы говорим о делегатах, переменные это или тип этих переменных.
Есть еще одна сложность с пониманием делегатов. Мы привыкли что в переменных у нас хранятся данные, а в переменной типа Делегат мы сохраняем функцию, а функция — это блок кода. Получается, что мы переводим код в разряд данных, а это, в каком то смысле, требует перехода на новый уровень понимания методов программирования.
Зачем это нужно если это так сложно?
Если вы профессионал своего дела вы должны знать как можно больше методов реализации логики, чтобы иметь возможность максимально эффективно решать текущие задачи или чтобы понимать чужие решения, с которыми вам приходится иметь дело. Вы собираетесь решать сложные задачи? Или нет? Сложные задачи требуют соответствующего уровня решений. По крайней мере я так думаю.
Определение делегата в рамках С++
Опять же, опираясь на то, что выделил Джон Скит в определении делегата, и с чем я вполне согласен, мы можем сосредоточиться на том, что мы хотели бы выделить в рамках концепции переменных для хранения/передачи методов на С++. Статья Как должен выглядеть делегат на C++ показывает нам только одно из решений основанное на С++ темплейтах, мы же попробуем понять что нужно добавить в С++ стандарт, чтобы иметь возможность объявлять переменные, которые способны принимать метод произвольного класса как значение, чтобы впоследствии иметь возможность вызвать этот метод произвольного класса как функцию через такую переменную.
Если обратиться к примерам кода, то наша задача выглядит очень просто, предложенное в 2011 году решение выглядит так
Victim test_class;
Delegate test_delegate;
test_delegate.Connect(&test_class, &Victim::Foo);
test_delegate();
нам надо понять, что нужно чтобы можно было писать примерно так:
Victim test_class;
DelegateType test_delegate = &test_class.Foo;
test_delegate();
В конце концов, возможно ли такое расширение языка С++ в принципе, и что нужно добавить в компиляторы языка, чтобы оно стало возможным.
Чтобы понять это нам придется вспомнить (или ознакомиться для тех, кто не знает) что такое указатели на функцию в С++ (а до этого в чистом С).
Пожалуй, на С++ нет ничего сложнее чем указатели на функции и особенно на функции члены класса, пожалуй, если забыть про темплейты и их спецификации. Проблему позволяет сгладить возможность задать псевдоним типа с помощью typedef, но сгладить это, ни в коем случае, не решить, к сожалению! Для примера я приведу код, который к тому же можно проанализировать на предмет совместимости со стилем объявления делегатов на C#:
class clsA
{
public:
int funcA(double p) { return (int)p+7; }
};
class clsB
{
public:
int funcB(double p) { return (int)p+9; }
};
typedef int(clsB::* delegatClsB)(double);
int main()
{
clsA cA;
int(clsA::* fPtrX1)(double) = &clsA::funcA;
int res = (cA.*fPtrX1)(123);
printf("funcA Ptr returns '%d'\n", res);
clsB cB;
delegatClsB fPtrX2 = &clsB::funcB;
res = (cB.*fPtrX2)(123);
printf("funcB Ptr returns '%d'\n", res);
return 0;
}
В случаях, когда используются указатели на С-функции (C# делегаты не исключение!) нам все равно, так или иначе, приходится анализировать соответствие очень многословных типов во многих случаях. Интересно отметить, что тип: сигнатура функции — это тип, который задается списком типов. Хотя С-структура или даже класс с одними полями без методов тоже задаются списком типов своих полей, между списком типов, один из которых определяет структуру, а другой определяет параметры функции есть принципиальная разница в их использовании, функцию нельзя вызвать, не инициализировав все члены списка (все параметры – не будем отвлекаться на функции с переменным списком параметров, это исключение, которое подтверждает правило, потому что длина списка будет задана в первых обязательных параметрах)
Тут я могу поделиться практическим секретом, который помогает мне в анализе сложных типов и в проверке правильности разных преобразований типов. Секрет заключается в использовании компилятора и внимательном отношении к сообщениям об ошибках и предупреждениях при компиляции кода и сравнении этих сообщений с сообщениями другого компилятора, если ваш рабочий компилятор генерирует недостаточно информативные сообщения. Иногда даже, полезно внести ошибку в код намеренно, чтобы компилятор в сообщении об ошибке явно написал вам как он видит тип переменной-выражения, которое вы написали. Таким образом всегда можно проверить себя, по сути, спросив подсказку у компилятора. Это особенно актуально, когда вам приходится работать в течение короткого времени с кодом, написанным на разных языках или для разных компиляторов.
Если, к примеру, присвоить указателю на функцию, метод несоответствующей сигнатуры:
int(clsA::* fPtrX1)(double) = &clsB::funcB;
Компилятор подробно расскажет нам какие типы он определил у переменных-выражений чтобы мы определились что мы хотим с ними сделать:
1>C:\src\srcFile.cpp(132,29): error C2440: 'initializing': cannot convert from 'int (__thiscall clsB::* )(double)' to 'int (__thiscall clsA::* )(double)'
Так что же нам мешает на языке С++ получить указатель на функцию, которая является членом класса? То, что кроме собственно указателя на функцию нам нужно еще как-то сохранить объект, без которого эту функцию нельзя вызвать. Получается, что нам нужен какой-то составной указатель, который бы хранил и функцию, и объект, от которого эта функция должна вызываться. Проблема в том, что тип объекта на этапе создания такого составного указателя будет неизвестен. Мы должны объявить некоторый класс (структуру) которая будет хранить указатель на объект неизвестного типа Х, и для этого мы можем использовать void* , но вот тип поля, чтобы хранить указатель на функцию, не может быть обезличен, потому что при объявлении типа функции класса мы должны явно указать имя этого класса, у нас нет какого-то ключевого слова, чтобы задать произвольный класс, но предположим, что какой-то сверх новый С++ стандарт добавил нам такое ключевое слово, например UnknownCaller и мы можем объявить тип FuncPtr для хранения метода произвольного класса:
typedef int(UnknownCaller::* FuncPtr)(double);
и мы смогли бы использовать одну переменную сначала для инициализации методом из класса А, потом методом из класса B:
FuncPtr delegatClang = &clsA::funcA;
…
delegatClang = &clsB::funcB;
но это не решит проблему, потому что для вызова функции из переменной delegatClang нам понадобится объект класса, которому принадлежит эта функция как в этой строке из примера выше:
res = (cB.*fPtrX2)(123);
cB является объектом класса clsB. Дело в том, что указатель на метод класса в С++ не является полноценным абсолютным указателем. Указатель на метод (не статический) класса – это что-то вроде смещения к методу в памяти, выделенной объекту-объектам класса под код класса. А операция <.*> — это операция доступа по смещению.
Честно говоря, я даже не совсем понимаю зачем в С++ реализовали возможность создавать указатели на метод класса, так как эти ограничения фактически исключают практическую пользу от их использования. Было бы очень интересно, если бы кто-то поделился практическим примером использования таких указателей.
Получается, что в С++ стандарт нельзя добавить поддержку делегатов? Ответ: нет, кажется, можно!
Мне кажется, поддержку делегатов все-таки можно добавить в С++, при желании. Нам, конечно, понадобится новое ключевое слово, но самое главное нам понадобится новый тип указателей, то, что я бы назвал: сложные указатели (не путать с умными указателями).
Давайте рассмотрим пример гипотетического С++ кода, использующий новое ключевое слово delegate которое используется в двух разных контекстах:
delegate int(* delegateClsX)(double);
int main()
{
ClsB cB;
delegateClsX test_delegate = delegate (cB.FuncB);
test_delegate(0.5);
ClsА cА;
delegateClsX test_delegate = delegate (cА.FuncА);
test_delegate(0.5);
}
рассмотрим с точки зрения концепции указателей С++ (с точки зрения прямого доступа к памяти через указатели) и с точки зрения статической типизации, при которой компилятор проверяет соответствие типов в любых операциях и не допускает не реализованные преобразования типов.
Первое использование ключевого слова позволяет нам определить новый особенный тип указателя, сложный указатель. Сложный в том смысле что он должен содержать в себе два значения, одно — это указатель на объект класса, для которого будет вызван метод, который является вторым значением в составе такого сложного указателя.
То есть такой сложный указатель мы можем гипотетически представить как структуру с двумя полями:
struct Delegatе
{
Void *obj;
int UnknownCaller: (*funcPtr)( double p);
}
И чтобы инициализировать такую структуру как структуру нам снова понадобится то же ключевое слово delegate которое поможет компилятору интерпретировать операцию доступа к члену класса “.” (точка) или “->” в скобках для проверки возможности такого доступа и для инициализации этой структуры двумя связанными значениями. При этом компилятор должен будет проверить сигнатуру переданного метода на соответствие сигнатуре функции, с которой объявлен тип переменной. Также нужно отметить, что для компиляции инициализации не нужны типы параметров и возвращаемого значения сигнатуры функции, но они нужны чтобы соблюдался принцип статической типизации. Соответствие типа переменной типу метода, предоставленного для инициализации этой переменной, должно быть проверено при компиляции.
Казалось бы, тут кроется проблема, в том, что при вызове через такой объект компилятор должен знать настоящий тип объекта, для которого надо вызвать метод. Но тут надо понимать, что при компиляции объект, для которого вызывается метод передается в этот метод в качестве первого параметра в виде указателя на этот объект, то есть в результате компиляции вызова, через такой сложный указатель, мы бы получили, в нашем случае, что-то вроде:
funcPtr(obj, 0.5);
или если подставить значения полей из примера:
funcB((void *)&cB, 0.5);
То есть для генерации кода вызова в этом случае компилятору тип не нужен!
Вопрос в том должен ли компилятор проверять тип указателя на объект, который он будет передавать как первый параметр функции! Или, другими словами, возможно ли что объект, который сохранен в поле obj нашей гипотетической структуры не будет соответствовать методу, который сохранен в поле funcPtr? Очевидно, что ответом на этот вопрос будет: нет, это невозможно! Это соответствие нам будет гарантировать уникальный способ инициализации нашей гипотетической структуры с новым ключевым словом.
Таким образом, с точки зрения базовых концепций языка С++ мы нашли способ, который позволяет создать расширение языка С++ на уровне компиляторов для поддержки делегатов в стиле C#.
Мне очень интересно узнать не упустил ли я чего-то в своих рассуждениях, что может опровергнуть мои выводы!
В каком смысле интерфейсы можно рассматривать как делегаты?
В предыдущем разделе мы в некотором смысле выяснили-разобрали что такое C# делегат через отображение этой сущности из C# в С++. Теперь давайте попробуем проанализировать понятие «интерфейс» тем же методом. Интерфейсы распространены гораздо шире, например в Java это фактически базовый элемент построения программ. В С++ нет специального ключевого слова для определения интерфейса, но интерфейсом является абстрактный базовый класс без полей данных.
Если рассматривать нашу исходную задачу не особо вдаваясь в подробности, просто как задачу по созданию переменной, через которую можно передавать методы разных классов то она решается и с помощью интерфейсов. Вот пример, сначала на Java:
interface clsI
{
int funcI(double p);
};
class clsA implements clsI {
public int funcI(double p) { return (int)p+7; }
}
class clsB implements clsI {
public int funcI(double p) { return (int)p+9; }
}
public static void main(String[] args) {
Main mn = new Main();
mn.mainMember();
}
public void mainMember(){
clsI delegate;
clsA cA = new clsA();
delegate = cA;
int res = delegate.funcI(0.5);
System.out.println("funcA Ptr returns "+ res + "\n");
clsB cB = new clsB();
delegate = cB;
res = delegate.funcI(0.5);
System.out.println("funcA Ptr returns "+ res + "\n");
}
А теперь то же самое на С++:
class clsI
{
public:
virtual int funcI(double p) = 0;
};
class clsA: public clsI
{
public:
int funcI(double p) { return (int)p+7; }
};
class clsB : public clsI
{
public:
int funcI(double p) { return (int)p+9; }
};
int main()
{
clsI* delegate;
clsA cA;
delegate = &cA;
int res = delegate->funcI(0.5);
printf("funcA Ptr returns '%d'\n", res);
clsB cB;
delegate = &cB;
res = delegate->funcI(0.5);
printf("funcB Ptr returns '%d'\n", res);
return 0;
}
Оба примера должны компилироваться и работать если я не напутал что-то при копировании на сайт.
Фактически тут мы доказали, что можно создать переменную (с именем delegate), в которой мы можем сохранить методы объектов разных классов, то есть задача в общем решена. Но это решение накладывает ряд ограничений на реализацию классов и их методов, которые задействованы для такого решения:
Классы должны наследоваться от общего интерфейса;
Методы должны иметь одно и то же имя, заданное в интерфейсе.
Эти требования не всегда можно выполнить, поэтому такое решение имеет ограниченную область применения по сравнению с C# делегатами, но эти ограничения не помешали известному иностранному специалисту сравнить реализацию замыканий на C# с делегатами и на Java с интерфейсами, как мы могли видеть в его статье.
И я хочу еще обратить ваше внимание что переменная «интерфейс» по сути является таким «сложным указателем», который я описал в предыдущем разделе. Обратите внимание, при вызове, который одинаково выглядит и на С++ и на Java и на C#:
res = delegate->funcI(0.5);
переменная delegate выступает в двух ипостасях:
она дает нам доступ к своему члену, к функции funcI в примере
она же будет передана в вызванную функцию класса в качестве указателя this на данные класса. Если бы мы определили какие-то поля в классах clsA или clsB мы бы могли к ним обращаться внутри реализации функций именно через этот указатель this , который мы получили из переменной delegate.
Указатель (ссылка) на интерфейс для компилятора является составным указателем, он состоит из
списка указателей (ссылок) на все функции класса, реализующего интерфейс и
указателя (ссылки) на данные объекта класса.
Я надеюсь такой способ анализа интерфейсов и делегатов может быть полезен кому-то при решении-анализе практических задач. По крайней мере мне иногда помогает.
Подведем итог
Мы рассмотрели возможность реализации делегатов в стиле C# на С++ и не обнаружили явных противоречий между концепцией делегатов и парадигмой языка С++. Из чего можно сделать вывод что делегаты, наверно, можно добавить в С++ на уровне компилятора.
Интерфейсы в какой-то степени покрывают функциональность делегатов, по крайней мере их можно использовать чтобы создавать переменные, которые позволяют сохранять методы разных (хоть и связанных через наследование одного интерфейса) классов.
Мы ввели-определели понятие "сложный или составной указатель", которое, возможно, кому-то поможет в понимании классов, интерфейсов, делегатов.
Пишите в комментариях если я что-то напутал, будем разбираться вместе.
Ответы на вопросы в коментариях
Я увидел статью долго рассуждающую как создать делегаты, что придется добавить в язык, и т.д. При этом в С++ уже есть std::function
Возможно не найдя ответа почему для реализации делегатов вы не использовали std::function
А что std::function разрешает такой синтаксис:
DelegateType test_delegate = &test_class.Foo;
или
delegateClsX test_delegate = delegate (cB.FuncB);
Разве можно объекту std::function присвоить функцию класса?
Вы ничего не просмотрели уважаемые @mentin и @Zekoriили цель просто придраться?
Единственное чего нет в С++ это короткого C# синтаксиса для тривиальной лямбды, object.Method, std::bind всё же чуть длиннее.
@mentin я могу надеяться, что это значит, что вы разрешили мне оставить эту мою статью на Хабре?
И, кстати, здесь вы ошибаетесь:
Для нестатических функций можно использовать std::bind, или лямбду в духе C#.
TestClass foo;
std::function<int(int)> g = [&foo](int x) { return foo.Baz(x); };
g(42);
C# не создает лямбду (функцию для вызова функции) для вызова даже такой (не статической) функции класса. В C# происходит то, что я описал в статье, хотя бы потому, что этот способ лучше с точки зрения производительности, а еще этот способ лучше оптимизируется с учетом контекста, но это совсем другая история.
Вы представьте, что наш с вами делегат надо вызывать милион раз в секунду, а у вас придется вызывать две вложенные функции, вместо одной сохраненной в C# делегате.
Меня когда-то учили, что надо по возможности избегать дополнительных вызовов, чтобы не было проблем с производительностью.
Поэтому вы написали делегат в стиле Java, а не в стиле C#.
Ответ для @eao197
А почему это std::function и std::bind нельзя считать стандартными средствами?
Начнем с того, что я нигде не писал, что их «нельзя считать стандартными средствами», то есть вы уже передергиваете! Но бог с ним, я все же напишу почему не все согласятся «считать их стандартными средствами»:
Некоторые могут сказать, что стандартными средствами надо считать то, что было до С++11, например;
В качестве второго варианта смотрите мой ответ на комментарий @mentin
Единственное чего нет в С++ это короткого C# синтаксиса для тривиальной лямбды, object.Method, std::bind всё же чуть длиннее.
чуть выше. Хотя уже в этом утверждении можно найти ответ, при желании. И я делаю логичный вывод, по моему, что просто желания нет.
Комментарии (19)
ptr128
30.10.2023 02:05+1Было бы очень интересно, если бы кто-то поделился практическим примером использования таких указателей.
Так же как и на C, когда зависимость между кодом команды, события или сообщения и функцией/методом, которая должна его обрабатывать, выясняется только при выполнении программы.
И часто функции обработки при этом живут в динамически загружаемой so, о которой на этапе компиляции основной программы ничего не известно, кроме допустимых типов функций.
KanuTaH
30.10.2023 02:05+2В Qt указатели на метод класса широко используются в качестве аргументов
connect()
для создания связей типа сигнал-слот. Вообще автор статьи по-видимому имеет очень ограниченный опыт работы с C++, и перед тем, как писать статьи подобного плана, ему следовало бы получше ознакомиться с матчастью.
mentin
А чем std::function<> не нравится?
MiraclePtr
Вот у меня тот же вопрос возник.
Ну ладно, в 2011 году (когда была написана первая статья, на которую ссылается автор) C++11 ещё только-только появлялся и почти нигде не поддерживался, но сейчас, по прошествии 12 лет, у нас везде есть std::function + std::bind, с которыми можно сделать то, что пытается сделать автор, только полностью стандартными средствами, легко и без изобретения велосипедов. А если мы bind'им только this, то там ещё даже дополнительных аллокаций не будет, в большинстве реализаций оно соптимизируется.
DancingOnWater
А зачем bind когда есть лямбды?
MiraclePtr
Тоже вариант. Я бы сказал, в каких-то случаях удобнее одно, в каких-то другое.
rukhi7 Автор
То есть вы считаете что, пытаться повторить C# делегаты С++ это изобретать велосипед?
std::function + std::bind это НЕ тоже самое что C# делегат, по моему. Вот даже ваш коллега по критике это частично признал. С моей точки зрения это то же создание обертки над функцией класса, обертки которую каждый раз придется разворачивать и заворачивать при вызове не статической функции класса из такого накрученного делегата. А обертку можно было создать и до С++11, и да, это то, что я хотел продемонстрировать, в том числе. Хорошо что вы обратили на это внимание.
Почему-то Jon Skeet не согласен, что пытаться повторить C# делегаты — это изобретать велосипед, он предпочитает использовать делегаты там, где они есть.
Вообще, насколько std::function и std::bind можно считать стандартными средствами, это очень интересный вопрос. Я думаю найдется немало практикующих разработчиков, которые с вами не согласятся, жаль что их не так много осталось на Хабре.
Еще, интересная манера у критиков писать притензии к статье о том, чего там нет! Я вам что экстрасенс, угадать то чего бы, вы (хоть вас и трое, хотя начал один двое поддержали) вдруг придумали, что вам надо обязательно найти в моей статье?
Мне поэтому кажется что вы намеренно уводите в сторону обсуждение темы статьи, просто потому что тема вам почему-то не нравится или не удобна. Это грустно!
Мешаю я вам со своими статьями, контингент особенных специалистов (КОС) разводить, так бы и сказали!
Наверно для стрижки разводить.
eao197
А почему это std::function и std::bind нельзя считать стандартными средствами?
Zekori
Смотрите, название статьи говорит о расширении стандарта (а стоит ли?). Вам пояснили что того же эффекта с делегатами можно добиться используя существующий функционал. Возможно в вашем опыте было то, что не покрывалось этим функционалом. И если бы вы им поделились было бы куда конструктивнее нежели говорить что все сообщество далеко от ваших ожиданий.
eao197
Я был бы очень признателен, если бы вы ответы на вопросы в комментариях давали в комментариях, т.к. у меня нет возможности вставить в текст вашей статьи свои возражения по поводу того, что вы мне приписали.
Если же говорить по сути, то:
Я не передергиваю ни грамма. Просто формулировка "насколько std::function и std::bind можно считать стандартными средствами, это очень интересный вопрос" означает, что если есть такой вопрос, то значит кто-то может не считать std::function и std::bind стандартными. А если так, то логично узнать "а почему?"
Вот я и задал вопрос "А почему?" минуя промежуточные рассуждения.
Вы ответили мне в тексте статьи и как прикажете теперь продолжать разговор на эту тему?
===
И да, на Habr-е, в отличии от, например, LOR-а, не принято напрямую указывать людям какое впечатление они производят. Но может вам хватит вести себя как капризному ребенку, который думает, что доносит до окружающего мира "правду" с большой буквы Пы, а его намеренно игнорируют, т.к. хотят эту правду скрыть. Такое впечатление вы производите, например, следующими высказываниями: "Мешаю я вам со своими статьями, контингент особенных специалистов (КОС) разводить, так бы и сказали!"
MiraclePtr
Они часть официального стандарта языка и его стандартной библиотеки, куда стандартнее то?
В статье Jon Skeet'а нет ни слова про C++. Он иплементит это на Java 6, в которой не было ни лямбд, ни нормального аналога плюсовой std::function.
Если вы сейчас не шутите, а пишете серьезно, то советую обратиться к психологам.
rukhi7 Автор
А вы не допускаете мысли, что кто-то может считать стандартом только исходный стандарт, а все последующие – расширениями стандарта? Только ваше мнение истинно?
А в С++ нет ни слова про делегаты, и что это доказывает?
Вас это беспокоит? Давайте об этом поговорим:
Если вы воспринимаете это как шутку, вы должны были посмеяться;
Если вы считаете, что мне надо к врачу, вы должны были мне посочувствовать;
Если вы считаете, что это глупая шутка, вы могли бы посмеяться надо мной или, опять же, посочувствовать;
Но вы выбрали вариант «устранить оппонента», и при этом мне тут рассказывают о каких-то неписаных правилах Хабра, которые я нарушаю.
Я просто наглядно демонстрирую варианты альтернативного смысла того, что вы написали, я не подменяю этот смысл как некоторые, у которых «нельзя считать» == «а почему». Не моя вина что выглядят эти варианты неприглядно.
В общем, похоже, мы выяснили что техническая сторона вопроса интересует вас только до тех пор, пока она не противоречит только вашим собственным представлениям о прекрасном.
Если что, меня вполне устроит этот мой комментарий в качестве моего последнего комментария на Хабре.
В общем, совсем огорчили вы меня! Если в этом была ваша цель, то вы преуспели знатно.
mayorovp
Хорошо, предположим, std::function и std::bind - всего лишь расширения стандарта. Но что предлагаете вы? Вы предлагаете сделать альтернативное расширение стандарта, верно?
Так почему вы, предлагая расширение стандарта, даже не удосужились обсудить отличия этого расширения от аналогичного и уже реализованного?
А теперь я этот вопрос возвращаю вам. По всему миру куча программистов считает C++11 стандартом. Разработчики компиляторов считают C++11 стандартом. Но вы упорно утверждаете что это всего лишь расширение к "истинному" стандарту. Только ваше мнение истинно?
eao197
Это можно допустить. Но всерьез воспринимать -- нет.
Не зря же в C++ есть несколько стандартов: С++98, С++03, С++11, С++14, С++17, С++20. То, что описано в рамках конкретного стандарта, является частью этого стандарта. По отношению к предыдущим стандартам это уже может быть не так. Посему std::function -- это часть стандарта C++ начиная с C++11, но это нестандартная штука для C++98/03. Равно как std::auto_ptr -- это часть стандартов 98/03/11/14, но не стандарт начиная с C++17.
Если у кого-то есть отличное мнение на этот счет, то таких товарищей (которые нам совсем не товарищи) следует отправлять к врачу. И нет, сочувствовать им не обязательно.
С такой тонкой душевной ориентацией нужно бы поосторожнее на публичных Интернет ресурсах выступать. Особенно в Рунете, где запросто могут и правду в глаза сказать не взирая на и не сочувствуя даже если.
rukhi7 Автор
а почему вы решили что std::function<> мне не нравится?
Тоже можно разобрать, сравнить с конструкциями из других языков.
Zekori
Возможно не найдя ответа почему для реализации делегатов вы не использовали std::function, лямбды с захватом this. Может есть то, что не позволяет использовать стандартный функционал?
mentin
Я увидел статью долго рассуждающую как создать делегаты, что придется добавить в язык, и т.д. При этом в С++ уже есть std::function, которая имхо ничем не уступает С# делегатам и обладает всеми их свойствами и удобным синтаксисом. Она уже есть, ничего добавлять в язык не надо.
Тут какое-то противоречие. Подумалось возможно чем-то std::function не подходит, что заставило обсуждать добавление в язык новых механизмов? В любом случае причины обсуждать необходимость новых механизмов, когда уже есть стандартные, стоило осветить в статье.
mentin
насчет добавленного вопроса про синтаксис, можно ли
да, можно. Судя по тому, что используется класс, а не объект - Foo статическая, и можно написать именно это:
Для нестатических функций можно использовать std::bind, или лямбду в духе C#.
При этом в делегат можно завернуть произвольно сложное выражение и захватить не один объект, а много, скажем
Единственное чего нет в С++ это короткого C# синтаксиса для тривиальной лямбды, object.Method, std::bind всё же чуть длиннее.
yatanai
Впринципе, для упрощения, можно понаделать мейков с мета-конструкторами, но это уже какой-то сюр будет, если честно.
Ничего не мешает даже свойства в С++ сделать, почти как в С#. Просто это всё излишнее колдунство)