Pull to refresh

Comments 40

Вот у меня тот же вопрос возник.

Ну ладно, в 2011 году (когда была написана первая статья, на которую ссылается автор) C++11 ещё только-только появлялся и почти нигде не поддерживался, но сейчас, по прошествии 12 лет, у нас везде есть std::function + std::bind, с которыми можно сделать то, что пытается сделать автор, только полностью стандартными средствами, легко и без изобретения велосипедов. А если мы bind'им только this, то там ещё даже дополнительных аллокаций не будет, в большинстве реализаций оно соптимизируется.

Тоже вариант. Я бы сказал, в каких-то случаях удобнее одно, в каких-то другое.

То есть вы считаете что, пытаться повторить C# делегаты С++ это изобретать велосипед?

std::function + std::bind это НЕ тоже самое что C# делегат, по моему. Вот даже ваш коллега по критике это частично признал. С моей точки зрения это то же создание обертки над функцией класса, обертки которую каждый раз придется разворачивать и заворачивать при вызове не статической функции класса из такого накрученного делегата. А обертку можно было создать и до С++11, и да, это то, что я хотел продемонстрировать, в том числе. Хорошо что вы обратили на это внимание.

Почему-то Jon Skeet не согласен, что пытаться повторить C# делегаты — это изобретать велосипед, он предпочитает использовать делегаты там, где они есть.

Вообще, насколько std::function и std::bind можно считать стандартными средствами, это очень интересный вопрос. Я думаю найдется немало практикующих разработчиков, которые с вами не согласятся, жаль что их не так много осталось на Хабре.

Еще, интересная манера у критиков писать притензии к статье о том, чего там нет! Я вам что экстрасенс, угадать то чего бы, вы (хоть вас и трое, хотя начал один двое поддержали) вдруг придумали, что вам надо обязательно найти в моей статье?

Мне поэтому кажется что вы намеренно уводите в сторону обсуждение темы статьи, просто потому что тема вам почему-то не нравится или не удобна. Это грустно!

Мешаю я вам со своими статьями, контингент особенных специалистов (КОС) разводить, так бы и сказали!

Наверно для стрижки разводить.

Вообще, насколько std::function и std::bind можно считать стандартными средствами, это очень интересный вопрос

А почему это std::function и std::bind нельзя считать стандартными средствами?

Смотрите, название статьи говорит о расширении стандарта (а стоит ли?). Вам пояснили что того же эффекта с делегатами можно добиться используя существующий функционал. Возможно в вашем опыте было то, что не покрывалось этим функционалом. И если бы вы им поделились было бы куда конструктивнее нежели говорить что все сообщество далеко от ваших ожиданий.

Я был бы очень признателен, если бы вы ответы на вопросы в комментариях давали в комментариях, т.к. у меня нет возможности вставить в текст вашей статьи свои возражения по поводу того, что вы мне приписали.

Если же говорить по сути, то:

Начнем с того, что я нигде не писал, что их «нельзя считать стандартными средствами», то есть вы уже передергиваете!

Я не передергиваю ни грамма. Просто формулировка "насколько std::function и std::bind можно считать стандартными средствами, это очень интересный вопрос" означает, что если есть такой вопрос, то значит кто-то может не считать std::function и std::bind стандартными. А если так, то логично узнать "а почему?"

Вот я и задал вопрос "А почему?" минуя промежуточные рассуждения.

Вы ответили мне в тексте статьи и как прикажете теперь продолжать разговор на эту тему?

===

И да, на Habr-е, в отличии от, например, LOR-а, не принято напрямую указывать людям какое впечатление они производят. Но может вам хватит вести себя как капризному ребенку, который думает, что доносит до окружающего мира "правду" с большой буквы Пы, а его намеренно игнорируют, т.к. хотят эту правду скрыть. Такое впечатление вы производите, например, следующими высказываниями: "Мешаю я вам со своими статьями, контингент особенных специалистов (КОС) разводить, так бы и сказали!"

Вообще, насколько std::function и std::bind можно считать стандартными средствами

Они часть официального стандарта языка и его стандартной библиотеки, куда стандартнее то?

Почему-то Jon Skeet не согласен, что пытаться повторить C# делегаты — это изобретать велосипед

В статье Jon Skeet'а нет ни слова про C++. Он иплементит это на Java 6, в которой не было ни лямбд, ни нормального аналога плюсовой std::function.

Мне поэтому кажется что вы намеренно уводите в сторону обсуждение темы статьи, просто потому что тема вам почему-то не нравится или не удобна. Это грустно! Мешаю я вам со своими статьями, контингент особенных специалистов (КОС) разводить, так бы и сказали! Наверно для стрижки разводить.

Если вы сейчас не шутите, а пишете серьезно, то советую обратиться к психологам.

Они часть официального стандарта языка и его стандартной библиотеки, куда стандартнее то?

А вы не допускаете мысли, что кто-то может считать стандартом только исходный стандарт, а все последующие – расширениями стандарта? Только ваше мнение истинно?

В статье Jon Skeet'а нет ни слова про C++. Он иплементит это на Java 6, в которой не было ни лямбд, ни нормального аналога плюсовой std::function.

А в С++ нет ни слова про делегаты, и что это доказывает?

Если вы сейчас не шутите, а пишете серьезно, то советую обратиться к психологам.

Вас это беспокоит? Давайте об этом поговорим:

  1. Если вы воспринимаете это как шутку, вы должны были посмеяться;

  2. Если вы считаете, что мне надо к врачу, вы должны были мне посочувствовать;

  3. Если вы считаете, что это глупая шутка, вы могли бы посмеяться надо мной или, опять же, посочувствовать;

Но вы выбрали вариант «устранить оппонента», и при этом мне тут рассказывают о каких-то неписаных правилах Хабра, которые я нарушаю.

Я просто наглядно демонстрирую варианты альтернативного смысла того, что вы написали, я не подменяю этот смысл как некоторые, у которых «нельзя считать» == «а почему». Не моя вина что выглядят эти варианты неприглядно.

В общем, похоже, мы выяснили что техническая сторона вопроса интересует вас только до тех пор, пока она не противоречит только вашим собственным представлениям о прекрасном.

Если что, меня вполне устроит этот мой комментарий в качестве моего последнего комментария на Хабре.

В общем, совсем огорчили вы меня! Если в этом была ваша цель, то вы преуспели знатно.

А вы не допускаете мысли, что кто-то может считать стандартом только исходный стандарт, а все последующие – расширениями стандарта? Только ваше мнение истинно?

Хорошо, предположим, std::function и std::bind - всего лишь расширения стандарта. Но что предлагаете вы? Вы предлагаете сделать альтернативное расширение стандарта, верно?

Так почему вы, предлагая расширение стандарта, даже не удосужились обсудить отличия этого расширения от аналогичного и уже реализованного?

Только ваше мнение истинно?

А теперь я этот вопрос возвращаю вам. По всему миру куча программистов считает C++11 стандартом. Разработчики компиляторов считают C++11 стандартом. Но вы упорно утверждаете что это всего лишь расширение к "истинному" стандарту. Только ваше мнение истинно?

Хорошо, предположим, std::function и std::bind - всего лишь расширения стандарта. Но что предлагаете вы? Вы предлагаете сделать альтернативное расширение стандарта, верно?

Не верно:

Если вы не заметили, статья называется “C# делегаты изнутри…”, это вроде как главное, если, конечно, вы мне разрешите, что-то решить по поводу моей статьи. Ну ладно, замнем для ясности.

Дело в том, что лично я никогда не видел в продуктовом-работающем коде эти расширения, эти стандартные решения (как вам больше нравится?). То есть не видел их на практике. Видите ли, я не могу предлагать читателю то, чего не видел, то, в чем, возможно, нет практического смысла!

Если вы сможете найти какой-то более-менее адекватный пример их практического использования, а вы ведь подаете это так, что это просто какое-то ключевое достижение в С++, о котором нельзя молчать! Так вот, если вы приведете такой пример, я обещаю вам, что покажу, как то же самое, можно сделать в той технике, которой меня когда-то научили. Я практически уверен, что у меня получится более эффективное решение, но давайте проверим. Давайте посмотрим, насколько вас интересуют технические вопросы.

А иначе…, иначе мне придется согласиться с мнением, что боты рулят на Хабре, уж извините! Интересно поддержит ли кто-то меня: @MAXH0 , @ne555 , @FadeToBlack? Я думаю, пора приглашать зрителей на этот Батл!

Так почему вы, предлагая расширение стандарта, даже не удосужились обсудить отличия этого расширения от аналогичного и уже реализованного?

А почему вы указываете что я должен был обсудить в моей статье??? Давайте я вам расскажу, что вы должны были написать в этом комментарии? Вам понравится?

Вы что меня в плен взяли? Или вы считаете, что если вас больше, то вы вправе распоряжаться?

А теперь я этот вопрос возвращаю вам. По всему миру куча программистов считает C++11 стандартом. Разработчики компиляторов считают C++11 стандартом. Но вы упорно утверждаете что это всего лишь расширение к "истинному" стандарту. Только ваше мнение истинно?

«По всему миру куча программистов считает…» это конечно убийственный аргумент…, где-то в начальной школе, но…

но тут вы повторяетесь! Я уже просил не придумывать то, чего я не писал! Я ничего не утверждал, по этому поводу. Читайте внимательно! Зачем вы свои качества хотите мне приписать?

Дело в том, что лично я никогда не видел в продуктовом-работающем коде эти расширения, эти стандартные решения (как вам больше нравится?).

Простите, чего вы не видели в продуктовом-работающем коде, std::function и std::bind?

Если вы не заметили, статья называется “C# делегаты изнутри…”, это вроде как главное, если, конечно, вы мне разрешите, что-то решить по поводу моей статьи. Ну ладно, замнем для ясности.

Хм, перечитал по новой и, совсем ничего не нашел про то, как делегат в C# устроен изнутри, т.е. как делегат выглядит на уровне CIL и что происходит в CLR\Mono когда оно выполняетт соответсвующие инструкции. Да и весь рассказ про делегаты в C# сводится к предложению: "Там делегаты определяются с помощью ключевого слова delegate".

И да, коль я перечитал еще раз статью, хотелось прокомментировать интересный момент:

Есть еще одна сложность с пониманием делегатов. Мы привыкли что в переменных у нас хранятся данные, а в переменной типа Делегат мы сохраняем функцию, а функция — это блок кода. 

А вы думали почему указатели на функцию и методы в Си++ такие как есть?

Дело в том, что лично я никогда не видел в продуктовом-работающем коде эти расширения, эти стандартные решения (как вам больше нравится?)

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

Видите ли, я не могу предлагать читателю то, чего не видел, то, в чем, возможно, нет практического смысла!

Современный стандарт языка C++, документацию по стандартной библиотеке и примеры ее использования можно легко найти в интернете. Более того, об этом написано немало книг с подробным разбором и примерами. Если вы позиционируете себя как C++-разработчика, то наверное все-таки нужно знать и понимать инструмент, который вы используете в работе, не правда ли?

А если вы не уверены в том, как работают какие-либо стандартные фичи языка, работают ли они вообще и есть ли в них смысл, то что мешало попробовать и проверить, прежде чем с умным видом кидаться переизобретать велосипеды и учить других?

Я стесняюсь спросить, может вы еще и лямбды в продакшн-коде не видели и считаете, что они не имеют практического смысла?

Если вы сможете найти какой-то более-менее адекватный пример их практического использования

Ровно для того же, для чего и делегаты в C# - предикаты, обработчики событий и коллбэки. При написании асинхронного кода (пока не перешли на корутины) их используют вообще на каждом углу, даже в примерах libasio, что с лямбдами, что с bind.

А почему вы указываете что я должен был обсудить в моей статье???

Разместили статью на публичном ресурсе с открытыми комментариями - готовьтесь принять обоснованную критику.

Да, IT-сообщество иногда может быть немного токсичным. Там не любят тех, кто делает заявления не разобравшись в вопросе, не любят велосипедостроителей без нужды, и не любят тех, кто огрызается в ответ на контр-аргументы и замечания по фактам. Если вы готовы к конструктивной дискуссии - вам тут будут рады, если нет - возможно на Пикабу или Одноклассниках ваши публикации зайдут лучше.

А иначе…, иначе мне придется согласиться с мнением, что боты рулят на Хабре, уж извините! 

Так самое смешное, что в этом ситуации именно вы ведете себя как бот. Вам пытаются аргументированно объяснить, в чем ваша ошибка, а вы в ответ огрызаетесь, кричите, переходите на личности и обвиняете всех вокруг во всех смертных грехах.

Что хочется сказать? Да, в С++ отсутствует понятие делегата, и самое близкое, что я встречал - это расширение в Borland C++ Builder - это closure. Я уже точно не помню, но оно было туда приделано на уровне расширения к синтаксису языка. Сам я лично делал все из перечисленного - сам писал функторы, позволяющие сохранять указатель на функцию члена класса, с использованием шаблонно-векторной магии в до-variadic эпоху. Код вот здесь. Не повторяйте этого дома! Используется для подписки на события UI. Вот здесь..

Сейчас, в принципе, std::function вместе с возможностью делать лямбды, которые делают захват указателя на инстанс класса выполняет эту роль. Автор данной темы всего лишь хочет сказать, что C++ - кривой язык программирования, который оброс костылями, в котором не хватает базовых вещей на синтаксическом уровне. И создатели языка не удосужились его не только изначально сделать нормальным, но и в попытках его улучшить воз с кобылой, если и движется, то движется под откос. Это первое.

А второе - сколько бы радикальным ни был автор статьи, сколь бы спорными ни были его комментарии - вы слили ему карму так, что он теперь в глубоком минусе. Достойное ли это поведение? Вместо того, чтобы продолжать беседу, вы заткнули ему рот. Я могу принимать или не принимать чью-то точку зрения. Но я не имею права объявлять свою точку зрения исключительно верной. И отбирать возможность выражать свои мысли у других. Это забирает у вас облик человека - вы, поддавшись эмоциям, совершаете агрессивное насильственное действие (наносите повреждение), ведущее к потере возможности пользователя проявлять себя в комментариях (умышленный вред виртуальному здоровью пользователя). Это, в результате, приводит к виртуальной смерти пользователя, так как после такого вы просто не в состоянии залечить эти раны. Если вам слили карму за правое дело (в общем-то!), то пойдете ли вы писать статьи для этих людей? Это насилие над свободой слова, и вам это нравится. Вы - варвары, вы - не достойны ни статей, ни общения с вами.

Спасибо! Нет слова чтобы описать какое большое "спасибо".

Я не совсем согласен, что "C++ - кривой язык программирования", или совсем не согласен, но нам, видимо, придется выбрать другую площадку для обсуждения этой темы!

Возможно вам понравится то, что у меня родилось когда мне в первый раз присвоили ReadOnly, по поводу, того как мне "не повезло с кармой":

По сравнению с тем как не повезло с кармой А.С. Пушкину, я счастливчик :) !

Если вы не согласны с тем, что с++ кривой язык... Вы должны огласить список языков, с которыми вы знакомы, а далее - рассказать, почему они хуже. Например, C# - синтаксически приятный язык, с очень крутыми фичами. Покрывает 99% любой разработки. И тут не дело личной симпатии. Я программирую на С++, я люблю программировать на нем. Но это не отменяет того, что этот язык - кривой. Что его и не пытаются сделать нормальным. Что хочется std библиотеку push_back (вернуть взад, а не то, что вы подумали) всем, кто ее писал, и кто придумывал названия функциям. C++ - ходячий труп, и от него вскоре откажутся. И в том числе, за неоправданную сложность.

Вы должны огласить список языков, с которыми вы знакомы, а далее - рассказать, почему они хуже

Нет, это не единственный вариант объяснения, мне представляется.

C# это просто мечта, а не язык, по моему мнению. Я на нем в основном и пишу последнее время, иногда в паре с WPF даже. Но только вы не сможете на нем написать реализацию какого-нибудь хоть сколько-нибудь аппаратно зависимого DirectX интерфейса, например. Просто что-нибудь хоть сколько-нибудь аппаратно зависимое написать вряд ли можно на C#. А вот на С++ пожалуйста, несмотря на все эти новомодные расширения. Я тут недавно придумал аргумент примерно по такой теме:

 в каком еще языке кроме C/C++ разрешены ассемблерные вставки?

Я просто одно время целые функции писал на ассемблере в рамках С++ проекта с COM объектами. И не зря писал – библиотеку с этими функциями купила одна всем известная компания. Я точно знаю, что в том проекте лямбды и фанкторы всякие новомодные были и будут (проект продолжается!) как ни пришей не пристегни, потому что они обычно, несовместимы с требованиями производительности (можно почитать мою самую первую статью, там упоминается задача).

То есть суть в том, что у каждого инструмента есть своя область применения.

Пытаться устроить соревнование между С++ и C# или Java-й или еще с каким бы то ни было высокоуровневым языком в этом смысле не очень умно, я бы сказал.

Меня тоже удручает что расширения исходного С++ стандарта, оказываются важнее чем то, для чего язык изначально был предназначен.

Но мы продолжаем дискуссию в очень токсичной среде, а я там добавился к вам в проект на Гите.

Кстати, токсичность зависит от Хаба, по моим наблюдениям (может от овнеров хаба), посмотрите дискуссию к моей самой первой статье, опять же.

Автор данной темы всего лишь хочет сказать, что C++ - кривой язык программирования, который оброс костылями, в котором не хватает базовых вещей на синтаксическом уровне. И создатели языка не удосужились его не только изначально сделать нормальным, но и в попытках его улучшить воз с кобылой, если и движется, то движется под откос.

Это просто-напросто убогий и совершенно необоснованный наброс на вентилятор, причем необоснованный в том числе потому, что автор, совершенно очевидно, C++ не знает, а когда ему указывают на очевидные ошибки, он начинает кривляться в стиле "за настоящий C++ я признаю только C с классами образца 98 года". За это карма автора и "движется под откос" (и уже походу там). Не то чтобы я был фанатом слива карм, но я понимаю резон минусующих.

Но это не отменяет того, что этот язык - кривой. Что его и не пытаются сделать нормальным. Что хочется std библиотеку push_back (вернуть взад, а не то, что вы подумали) всем, кто ее писал, и кто придумывал названия функциям. C++ - ходячий труп, и от него вскоре откажутся. И в том числе, за неоправданную сложность.

Еще один убогий наброс в том же стиле.

Например, C# - синтаксически приятный язык, с очень крутыми фичами.

Если бы я занимался убогими набросами, как вы с автором статьи, то я бы сказал, что C# - это язык мечты для людей, застрявших примерно в середине нулевых и не продвинувшихся дальше "C с классами". Но я не буду этим заниматься, а просто предложу простенькую задачку, которую предлагаю вам реализовать на "синтаксически приятном языке с крутыми фичами". А задачка такова: написать функцию, которая принимает произвольное количество (в том числе и больше 8) аргументов произвольных типов (т.е. типы аргументов могут различаться), и возвращает кортеж, содержащий все переданные аргументы c сохранением типов, только в обратном порядке. То есть, например, если условную функцию вызвали так:

SomeType a = ...;
SomeOtherType b = ...;
int c = ...;
double d = ...;

foo(a, b, c, d);

то она должна вернуть кортеж (double, int, SomeOtherType, SomeType). На "кривом языке программирования, в котором не хватает базовых вещей на синтаксическом уровне" тело наивной реализации такой функции занимает ровно 4 строчки.

Но это не отменяет того, что этот язык - кривой. Что его и не пытаются сделать нормальным. Что хочется std библиотеку push_back (вернуть взад, а не то, что вы подумали) всем, кто ее писал, и кто придумывал названия функциям.

Кстати об этом, вторая задачка для вас. Вот код на "кривом языке, std библиотеку которого вы хотите push_back ее создателям":

std::map<int, double> m;

[...]

const auto [iter, inserted] = m.try_emplace(key, val);
if (inserted) {
    return;
}

if (iter->second < val) {
    iter->second = val;
}

Что здесь происходит - думаю, понятно: происходит обновление значения для некоего ключа в std::map в том случае, если предыдущее значение меньше нового (или соответствующего элемента в m нет вовсе). Обратите внимание: lookup по m здесь происходит ровно один раз. Пожалуйста, продемонстрируйте, как написать такой же эффективный код с Dictionary из "синтаксически приятного языка с очень крутыми фичами".

Обратите внимание: lookup по m здесь происходит ровно один раз. Пожалуйста, продемонстрируйте, как написать такой же эффективный код с Dictionary

Если бы вы внимательно прочитали документацию на Dictionary вы бы заметили что операция доступа к элементу m[key] для него является практически О(1) операцией:

Getting or setting the value of this property approaches an O(1) operation.

Поэтому такой же эффективный код на C# будет выглядеть примерно вот так:

Dictionary<int, double> m;

[...]

Bool inserted = m. TryAdd(key, val);
if (inserted) {
    return;
}

if (m[key] < val) {
    m[key] = val;
}

А если вы его скомпилируете в конфигурации Release, я не удивлюсь что он окажется даже более эффективным чем версия на С++.

Поэтому такой же эффективный код

Да неужели? Lookup по Dictionary здесь происходит далеко не один раз. Вот это что такое:

if (m[key] < val) {
    m[key] = val;
}

? Это два совершенно лишних lookup'а, которых в C++ версии просто нет.

Если бы вы внимательно прочитали документацию на Dictionary вы бы заметили что операция доступа к элементу m[key] для него является практически О(1) операцией

Dictionary реализована через Hashtable, поэтому "практически O(1)" там будет только если хеш-функция не подведет. Если хотите именно хеш-таблицу, то просто замените в моем примере std::map наstd::unordered_map. Будет по-прежнему всего один lookup, в отличие от вашего кода на C#.

P.S. Задачу про кортеж тоже ниасилили? Понимаю.

? Это два совершенно лишних lookup'а, которых в C++ версии просто нет.

Dictionary реализована через Hashtable, поэтому "практически O(1)" там будет только если хеш-функция не подведет.

Я бы с вами со всем моим удовольствием согласился бы, если бы вы мне рассказали как оптимизация в C# работает в Release.

Но вы, к сожалению, не расскажите. Поэтому предлагаю боевую ничью. :)

А вот я кстати ради любопытства сделал несколько тестов. Когда хеш-функция удачная и коллизий нет, C++ версия работает примерно на равных с версией на C#, но как только появляются коллизии, у C# все становится очень плохо из-за дополнительных поисков в тех самых двух строчках.

Вот тестовый код на C#
namespace Application {
    struct Key
    {
        public int _idx;

        public Key(int idx) => _idx = idx;

        public override int GetHashCode()
        {
            return _idx % 10;
        }

        public override bool Equals(object? other)
        {
            return _idx == ((Key)other)._idx;
        }
    }

    class Program
    {
        public static void Main(string[] args)
        {
            Dictionary<Key, double> m = new Dictionary<Key, double>();

            for (int i = 0; i < 1000000; ++i) {
                Key key = new Key(i % 10000);
                double val = i;

                bool inserted = m.TryAdd(key, val);
                if (inserted) {
                    continue;
                }

                if (m[key] < val) {
                    m[key] = val;
                }
            }

            Console.WriteLine(m.Count);
        }
    }
}

А вот аналогичный на C++
#include <iostream>
#include <unordered_map>

struct Key
{
    int _idx;

    Key(int idx) : _idx{idx} {};

    bool operator==(const Key &other) const
    {
        return _idx == other._idx;
    }
};

struct KeyHash
{
    size_t operator()(const Key &key) const noexcept
    {
        return key._idx % 10;
    }
};

int main()
{
    std::unordered_map<Key, double, KeyHash> m;

    for (int i = 0; i < 1000000; ++i) {
        double val = i;

        const auto [iter, inserted] = m.try_emplace(i % 10000, val);
        if (inserted) {
            continue;
        }

        if (iter->second < val) {
            iter->second = val;
        }
    }

    std::cout << m.size() << " " << std::endl;

    for (size_t b = 0; b < m.bucket_count(); ++b) {
        size_t sz = m.bucket_size(b);

        if (sz == 0) {
            continue;
        }

        std::cout << "BUCKET " << b << ": " << sz << std::endl;
    }
}

Вот результат C++:

$ g++ -Wall -O2 map.cpp

$ time ./a.out
10000
BUCKET 0: 1000
BUCKET 1: 1000
BUCKET 2: 1000
BUCKET 3: 1000
BUCKET 4: 1000
BUCKET 5: 1000
BUCKET 6: 1000
BUCKET 7: 1000
BUCKET 8: 1000
BUCKET 9: 1000

real    0m4,619s
user    0m4,617s
sys     0m0,001s

Я специально печатаю количество элементов в hash bucket'ах, чтобы убедиться, что все коллизии на месте.

Вот результат C#:

$ dotnet build --configuration Release
MSBuild version 17.4.8+6918b863a for .NET
  Determining projects to restore...
  Restored dotnet.csproj (in 149 ms).
Program.cs(15,29): warning CS8605: Unboxing a possibly null value. [dotnet.csproj]
  dotnet -> bin/Release/net7.0/dotnet.dll

Build succeeded.

Program.cs(15,29): warning CS8605: Unboxing a possibly null value. [dotnet.csproj]
    1 Warning(s)
    0 Error(s)

Time Elapsed 00:00:04.90

$ time ./dotnet
10000

real    0m13,314s
user    0m12,865s
sys     0m0,424s

Тут я количество элементов в hash bucket'ах не печатаю, потому что в Dictionary такую информацию не получить, но в целом по таймингам уже все ясно - они всегда примерно в 3 раза больше чем у C++ кода. Сказываются те самые два дополнительных lookup'а. Пример конечно синтетический, но, думаю, вполне наглядный.

Вот это меня впечатляет! Впечатляет то что вы C# компилируете в той же среде в которой gcc работает. У меня то все просто я в ви-студии сижу.

И мне очень приятно что вы перешли на конструктивно-аргументированный язык дискуссии!

Исключительно в целях поиска объективности, я возьмусь предположить что на вашей платформе C# компилируется НЕ в нативный код(в ассемблер), как С++, а в промежуточный байт код, как Java! Соответственно, я думаю, полученная вами разница происходит именно из этого факта!

По крайней мере я бы проверил это предположение.

Вы уж извините, но ваш исходный тон все еще заставляет меня писать так официально - не по-человечески!

Соответственно, я думаю, полученная вами разница происходит именно из этого факта!

Да ну нет, конечно, JIT там отнимает копейки, десятки, может быть, сотню миллисекунд, это тут вообще ни о чем, а библиотечная реализация Dictionary уже скомпилирована в машинный код еще в Microsoft. Ну, если угодно, вот вам AOT-сборка:

$ dotnet publish --configuration Release --runtime linux-x64 --self-contained -p:PublishReadyToRun=true
MSBuild version 17.4.8+6918b863a for .NET
  Determining projects to restore...
  Restored dotnet.csproj (in 194 ms).
Program.cs(15,29): warning CS8605: Unboxing a possibly null value. [dotnet.csproj]
  dotnet -> bin/Release/net7.0/linux-x64/dotnet.dll
  dotnet -> bin/Release/net7.0/linux-x64/publish/

$ time ./dotnet
10000

real    0m12,453s
user    0m12,087s
sys     0m0,340s

$ time ./dotnet
10000

real    0m12,937s
user    0m12,563s
sys     0m0,344s

Нет, эта разница - это следствие чистой неэффективности C# кода из-за того что каждое обращение по [key], которое вы написали, убивает производительность при каждой коллизии, а когда ключом является что-нибудь сложное, строки или байтовые массивы, которые нужно превратить в хеш в виде всего лишь 32-битного int, коллизии будут обязательно.

Я не в коем случае не возьмусь утверждать, что вы неправы, тем более что вы демонстрируете результаты, которые очевидно заслуживают доверия.

А я не занимался такими детальными проверками, и у меня нет возможности их повторить, а тем более придумать что-то чтобы их уточнить.

Я исхожу из своих общих ощущений, из того что у меня лично не было проблем с происводительностью на C# для моих задач. При этом я и оптимизацией на С++ тоже занимаюсь, но у меня задачи из этих областей никогда не пересекались чтобы их так тщательно сравнивать.

Так что исключительно из этих моих ощущений я, пока, все равно буду сомневаться в ваших выводах. Пока это не мешает мне в моей практической работе, видимо.

Я исхожу из своих общих ощущений, из того что у меня лично не было проблем с происводительностью на C# для моих задач.

Ну а вот тут например у человека боль на эту тему. Не знаю, что у него там за ключ, но, видимо, коллизии имеют место быть и бьют по производительности. Но даже если он сделает то, что он хочет, через предлагаемые костыли в виде box'инга в том или ином виде, то при добавлении элемента у него все равно будет два обращения к Dictionary вместо всего одного в C++.

Ну а вот тут например у человека боль на эту тему.

Ну боль он сам себе создает запихивая 100 милионов элементов в словарь. Но, хорошо, рассматриваем как синтетический тест.

Если в вашем примере для эксперимента применить этот костыль с упаковкой и снова время измерить, очень интересно как оно изменится. По сравнению результатов уже можно будет судить, возможно, в чем причина задержки! Потому что если мы уберем 1 из трех лукапов (там же их три так как TryAdd тоже = m[key]), у нас должно время соответственно в 1.5 раза уменьшиться, казалось бы.

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

Вот это будет для меня доказательством.

А вообще, скажу вам по секрету, я надеялся, что оптимизация "увидит" повторное использование элемента из словаря в соседних строчках и сможет заменить две операции одной, вот если бы это можно было проверить ... .

А если оно не уменьшится то надо изобретать как все таки отделить влияние количества обращений к словарю, от влияния упаковки

Где вы в том коде увидели упаковку?

А вообще, скажу вам по секрету, я надеялся, что оптимизация "увидит" повторное использование элемента из словаря в соседних строчках и сможет заменить две операции одной

По секрету отвечу: нет шансов.

У вас какие-то странные "ощущения". Из общих ощущений три операции поиска всегда хуже чем одна. Всегда.

Независимо от того, AOT там или JIT. Независимо от того, O(1) там или O(N). Независимо ни от чего.

А вы не допускаете мысли, что кто-то может считать стандартом только исходный стандарт, а все последующие – расширениями стандарта?

Это можно допустить. Но всерьез воспринимать -- нет.
Не зря же в C++ есть несколько стандартов: С++98, С++03, С++11, С++14, С++17, С++20. То, что описано в рамках конкретного стандарта, является частью этого стандарта. По отношению к предыдущим стандартам это уже может быть не так. Посему std::function -- это часть стандарта C++ начиная с C++11, но это нестандартная штука для C++98/03. Равно как std::auto_ptr -- это часть стандартов 98/03/11/14, но не стандарт начиная с C++17.

Если у кого-то есть отличное мнение на этот счет, то таких товарищей (которые нам совсем не товарищи) следует отправлять к врачу. И нет, сочувствовать им не обязательно.

В общем, совсем огорчили вы меня!

С такой тонкой душевной ориентацией нужно бы поосторожнее на публичных Интернет ресурсах выступать. Особенно в Рунете, где запросто могут и правду в глаза сказать не взирая на и не сочувствуя даже если.

а почему вы решили что std::function<> мне не нравится?

Тоже можно разобрать, сравнить с конструкциями из других языков.

Возможно не найдя ответа почему для реализации делегатов вы не использовали std::function, лямбды с захватом this. Может есть то, что не позволяет использовать стандартный функционал?

Я увидел статью долго рассуждающую как создать делегаты, что придется добавить в язык, и т.д. При этом в С++ уже есть std::function, которая имхо ничем не уступает С# делегатам и обладает всеми их свойствами и удобным синтаксисом. Она уже есть, ничего добавлять в язык не надо.

Тут какое-то противоречие. Подумалось возможно чем-то std::function не подходит, что заставило обсуждать добавление в язык новых механизмов? В любом случае причины обсуждать необходимость новых механизмов, когда уже есть стандартные, стоило осветить в статье.

насчет добавленного вопроса про синтаксис, можно ли

DelegateType test_delegate = &test_class.Foo;

да, можно. Судя по тому, что используется класс, а не объект - Foo статическая, и можно написать именно это:

std::function<int(int)> f = &TestClass::Foo;
f(42);

Для нестатических функций можно использовать std::bind, или лямбду в духе C#.

TestClass foo;
std::function<int(int)> g = [&foo](int x) { return foo.Baz(x); };
g(42);

При этом в делегат можно завернуть произвольно сложное выражение и захватить не один объект, а много, скажем

TestClass foo;
vector<int> boo;
std::function<int(int)> g = [&](int x) { return foo.Baz(x * x) + boo[x]; };
g(42);

Единственное чего нет в С++ это короткого C# синтаксиса для тривиальной лямбды, object.Method, std::bind всё же чуть длиннее.

Впринципе, для упрощения, можно понаделать мейков с мета-конструкторами, но это уже какой-то сюр будет, если честно.

Ничего не мешает даже свойства в С++ сделать, почти как в С#. Просто это всё излишнее колдунство)

Было бы очень интересно, если бы кто-то поделился практическим примером использования таких указателей.

Так же как и на C, когда зависимость между кодом команды, события или сообщения и функцией/методом, которая должна его обрабатывать, выясняется только при выполнении программы.

И часто функции обработки при этом живут в динамически загружаемой so, о которой на этапе компиляции основной программы ничего не известно, кроме допустимых типов функций.

В Qt указатели на метод класса широко используются в качестве аргументов connect() для создания связей типа сигнал-слот. Вообще автор статьи по-видимому имеет очень ограниченный опыт работы с C++, и перед тем, как писать статьи подобного плана, ему следовало бы получше ознакомиться с матчастью.

Only those users with full accounts are able to leave comments. Log in, please.

Articles