Pull to refresh

Comments 108

Это всё, конечно, весело, но почему функции только для double? Или дельфи обобщённую версию не потянет?

for I := 0 to Pred(Length(Self)) do
for I := Length(Self) - 2 downto 0 do

Дельфи с индексами от нуля забавно выглядит. :)

Frac(X) = 0

Сравнивать double через равенство — это нормально?
Сравнивать double через равенство — это нормально?

Даже работать будет :) Правда не всегда…
К сожалению в Delphi массив это не класс и добавить к нему методы можно только через т.н. helper, а helper тупой, не умеет работать с обобщенным типом.
UFO just landed and posted this here
В паскале естественная индексация — с единицы, поэтому цикл for идеально приспособлен под неё: for i := 1 to N или for i := N downto 1. В сях естественная индексация с нуля, а циклу for вообще пофиг на индексы: хоть for (i = 0; i < N; i++), хоть for (i = 1; i <= N; i++). Когда же в дельфи индексация с нуля, получается глупость: язык не приспособлен для этого, и появляются странные и неестественные вычитания единиц.
Позвольте, но это в паскале или в бейсике, а в Delphi для массивов индексация идет с нуля.
С единицы индексируются только строки, если, конечно, не включена директива {$ZEROBASEDSTRINGS ON}
Э… Хотите сказать, всегда так было? В том дельфи, который я помню, индексы задавались явно… или это был паскаль… Если что, я к коду на дельфи уже много лет не прикасался, и даже в те тёмные времена в основном билдер мучал. Но меня всё равно удивляет несоответствие между языком и индексами.
Ага — в дельфях (32 битных) так всегда и было :)
Позвольте, но это в паскале или в бейсике, а в Delphi для массивов индексация идет с нуля.
А вы какие массивы имеете в виду — статические или динамические? Статические индексируются как пользователь задаст. Динамические да, с нуля.
Статические так же, но их принято с нуля задавать :)
А чтобы не задумываться, как он задан, есть удобные функции low() и high().
Когда же в дельфи индексация с нуля, получается глупость: язык не приспособлен для этого, и появляются странные и неестественные вычитания единиц.

Здесь опять-же сказывается отсутствие знаний в предметной области, и не понимание самого принципа работы Delphi компилятора, обрабатывающего такие конструкции.:)
Не обижайтесь — но вы действительно сказали не правильную мысль :)
Ну раскройте уж мысль. Мне жутко интересно, при чём тут вообще компилятор. Я считаю, что необходимость использовать при обходе массива функции pred или вычитания — это архитектурный недостаток, ухудшающий читаемоть кода (не то чтобы си здесь блистал, где легко спутать < и <=, но всё же). Я не прав?
Используйте Low и High.
Архитектурный недостаток… возможно. Но при чем здесь заточенность на массива с индексацией от 1?
Я с паскаля начинал свой путь программиста… и всегда считал нормой индексацию с 0.
Да и сами структуры паскаля, типа того же динамического массива, требуют именно с 0 начинать работу.
Потому что естественным для компилятора — вычислять адрес элемента по его индексу. В случае с индексацией с нуля адрес вычисляется как:
ElementPointer = ArrayPointer + Index*ElementSize
а при индексации с 1 компилятору нужно будет делать дополнительные приседания с вычитанием единицы:
ElementPointer = ArrayPointer + (Index-1)*ElementSize
Не бывает «естественно для компилятора». То, что вы описали — «естественно для машинного кода» (и то с натяжкой). А вот компилятор все эти индексы как раз должен скрывать, преобразовывая из удобного для программиста в удобное для компьютера. Весь смысл компилятора в этом.

Ну и таки в цикле for на уровне машинного кода обычно никаких «ArrayPointer + Index * ElementSize» нет, потому что это тормозное выражение ни для компилятора, ни для машинного кода естественным не является.
Вы сейчас про какие преобразования, рантайм или компайлтайм?
Далеко не все преобразования возможно провести во время компиляции. Например из хеш функции прилетает некоторый BucketIndex. Как ни крути — это будут либо накладные расходы на преобразование, либо ArrayPointer будет смещен на -BaseIndex*ElementSize, и тогда чтобы просто взять указатель на первый элемент массива его нужно будет опять считать.
Так и так накладные расходы, и чтобы минимизировать накладные расходы и быть ближе к железу — индексация с нуля.

Ну и таки в цикле for на уровне машинного кода обычно никаких «ArrayPointer + Index * ElementSize» нет, потому что это тормозное выражение ни для компилятора, ни для машинного кода естественным не является.
Мы сейчас про индексацию? Или конкретно вот этот цикл? Вы утверждаете, что индексация массива с нуля — архитектурный недостаток? То есть проблема не в том, что for использует только <= для сравнения, а именно в индексации, да?
Далеко не все преобразования возможно провести во время компиляции.

Поэтому я написал «обычно».

Вы утверждаете, что индексация массива с нуля — архитектурный недостаток?

Проблема в for, который синтаксически оптимизирован под явные границы индексов, а при индексации с нуля эти границы нужно вычислять в исходном коде (или low и high, или вычитание единицы). Непривычно видеть одну из самых банальных конструкций — обход массива — обёрнутой в синтаксический мусор.

Что индексация с нуля более оптимальна для компьютера — это и так понятно, с этим я не спорю.
Так и так накладные расходы, и чтобы минимизировать накладные расходы и быть ближе к железу — индексация с нуля.
На современных процессорах под современные ОС это экономия на спичках. А если учесть, что поправку достаточно вычислить единожды при входе в цикл — так и вовсе.
Гораздо продуктивнее в большинстве случаев будет оптимизация алгоритма, а не охота за минус одним.
Ну для начала — делфи создавался не сегодня. Да и зачем выворачивать все шиворот на выворот? Мне лично индексация с нуля удобна, вам нет?
Его там и нет, в обоих случаях. К примеру, вот обращение к элементам массива начинающихся с нуля:

var
  I: Integer;
  Buff: array [0..5] of Byte;
begin
  for I := 0 to 5 do
    Buff[I] := I;

асм листинг:

Unit1.pas.43: for I := 0 to 5 do
005DAA67 33D2             xor edx,edx
005DAA69 8BC4             mov eax,esp
Unit1.pas.44: Buff[I] := I;
005DAA6B 8810             mov [eax],dl
005DAA6D 42               inc edx
005DAA6E 40               inc eax
Unit1.pas.43: for I := 0 to 5 do
005DAA6F 83FA06           cmp edx,$06
005DAA72 75F7             jnz $005daa6b

вот то-же самое, но для массива индексирующегося с 10:

var
  I: Integer;
  Buff: array [10..15] of Byte;
begin
  for I := 10 to 15 do
    Buff[I] := I;

асм листинг:

Unit1.pas.34: for I := 10 to 15 do
005DAA4F BA0A000000       mov edx,$0000000a
005DAA54 8BC4             mov eax,esp
Unit1.pas.35: Buff[I] := I;
005DAA56 8810             mov [eax],dl
005DAA58 42               inc edx
005DAA59 40               inc eax
Unit1.pas.34: for I := 10 to 15 do
005DAA5A 83FA10           cmp edx,$10
005DAA5D 75F7             jnz $005daa56


как можно увидеть никаких вычитаний типа Pred и прочего тут попросту нет.
Я о том и говорю: компилятор любой цикл for спокойно оптимизирует: хоть с нулём, хоть с единицей, хоть я пятёркой; хоть с константами, хоть с low и high, хоть с вычитанием единицы. А вот в исходном коде гораздо приятнее иметь чистый цикл.
UFO just landed and posted this here
Чтобы не натыкаться на грабли, надо всегда использовать Low и High. Как для строк, так и для массивов.

for i := Low(X) to High(X)

for i := High(X) downto Low(X)


и проблем не будет.

Вообще, тенденция такова, что скоро строки будут 0-based, а прямое изменение строк — вообще запрещено. Т.е. нельзя будет сделать s[i] :=…
Вообще оно уже с 7-й делфи давно запрещено а S[i] реализуется через обертку-костыль и не всегда правильно работает.
Это как не всегда? Можно пример?
Да, что там за обертки?
У нас весь код основан на S[i] :=
и все работает прекрасно (текстовый редактор со спец. возможностями)
в хелпе к делфи черным по белому написано что конструкция S[i] небезопасна и оставлена только для обратной совместимости, а нужно и правильно использовать конструкции типа copy(S,i,1) которые оптимизируются компилятором где это возможно.

при помощи S[i] можно запросто вылезти в чужую память и не заметить этого, либо получить AV если не повезёт.
Ну во-первых, у нас всегда включены RangeChecks=ON, не уверен на 100%, влияют ли они на строки, но за границу дин. массива точно не вылезешь.
А во-вторых, давно ли Delphi стал супер-безопасным языком. Я могу использовать Pointer, Absolute, Move и еще кучу способов испортить память. Но я же понимаю, что так делать нужно только там, где это требуется.
И мне удобно так писать, не вижу тут ничего опасного:
Скрытый текст
function ConvertBadChars(const aStr: String): String;
var
  i: Integer;
begin
  Result := aStr;
  for i := 1 to Length(Result) do
    case Result[i] of
      '«', '»': Result[i] := '"';
      '„', '“', '”': Result[i] := '"';
      '–', '—': Result[i] := '-'; // тире на знак "минус"
      '‹': Result[i] := '<';
      '›': Result[i] := '>';
      '`', '‘', '’': Result[i] := ''''; // Апострофы разного вида
    end;
end;

Даже в релизе данная настройка включена?
Да, в релизе как и в дебаге включено всё по макусимуму Range Checks, Overflow Checks, Assertions. Что бы мы без этого делали, мы бы задолбались искать ошибки. Влияния данных опций на скорость работы не заметили.
На скорость, конечно не повлияет (ну тут как — скажем, это будет незаметно для пользователя), а вот делать конечного пользователя бетатестером — это тот еще юмор :)
Мы все комплексные тесты проводим со всеми включенными отладочными галками, включая ассерты, но в релиз все уходит в отключенном виде.
А для поиска ошибок (в релизе) мы используем EurekaLog.
а вот делать конечного пользователя бетатестером

Ну да, конечно лучше скрыть от него ошибки неправильно работающей программы, чтобы казалось, что оно работает, когда на самом деле не работает. Или чего лучше — бороться с гейзенбагом, когда то глючит, то нет.
Пусть уж лучше сразу свалится — мы сразу и исправим.
Не-не, я не про то говорю :)
Вот возьмем ситуацию с Overflow — и присвоим инту значение выше диапазопа $7FFFFFF — разве это повлияет на работоспособность ПО, тем более что в криптографии это вообще штатная и допустимая сущность?
Вообще, все ошибки связанные с переполнение должны быть покрыты еще на этапе альфатеста посредством юниттестов.
В моей практике еще ниразу не встретилось что-то что повлияло на работоспособность нашего ПО, после юнит-тестирования со всеми галками.
Поэтому и считаю данный шаг не только избыточным, но и вредным — лучше расширять нагрузочное тестирование, чем выдавать свои ляпы пользователю.
В клайнем случае — та-же EurekaLog спасет и покажет место.
Испортить то можно разными способами, но зачем лишний раз искушать судьбу?

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

То что у вас не «тормозит» означает только одно — работа со строками не является узким местом в программе.
Да, опции проверки диапазонов рассчитаны на работу только в режиме отладки — чтобы выявить откровенные косяки, а в остальном программа не должна ни при каких обстоятельствах допускать выход за границы диапазона — проверка границ это просто «колокольчик» оповещающий о проблемах в программе коих не должно быть по определению.
Ага, действительно, как только перешли на Delphi, с паскаля, так все и поменялось :)
Кстати, а зачем вы так удачно обошли механизм реаллока памяти, упомянув про конкатенацию? Это действительно так работает только в Дельфи? :)
Но впрочем я пожалуй смогу утверждать, что даже после кокатенации строк, что в сях, что в дельфи — это будет именно единый и непрерывный массив.
Хотите поспорить? :)
программа не должна ни при каких обстоятельствах допускать выход за границы диапазона

Программа не должна это, программа не должна то… Какая ересь. Программы пишут люди и постоянно допускают ошибки. Нет программ без ошибок, ну нету. Никакие синтетические тесты не выявят ошибок, происходящих на бесконечном множестве входных данных, которые возникают у пользователей.
Если бы тесты работали, не было бы так много часто выпускаемых обновлений винды, сервис паков Microsoft SQL Server и т.п., а уж у них, поверьте, автоматическое тестирование настроено как надо и работает гораздо круче чем, у простых смертных. Чтобы быстро выявлять любые ошибки мы при написании кода используем концепцию самоверифицируемого кода, т.е. кроме всех возможных автоматических проверок компилятора код постоянно наряду с рабочими действиями выполняет дополнительный код чисто по верификации данных (типа сложных Assert'ов). Ну конечно не весь код, а критичные участки, в которых ошибки ну вот прямо совсем недопустимы, т.к. например, они могут ввести систему в несогласованное состояние. Примером может быть какой-то участок криптоалгоритма, когда после выполнения ряда вычислений мы можем сразу провести обратные вычисления и сравнить с исходными данными, пока они «еще есть у нас в открытом виде».
Выход за пределы массива допущенный программистом — это только одна из тысяч проблем, и это та проблема которая довольно хорошо ловится тестами и соблюдением элементарных правил при написании кода.
Все те заплатки и дыры это преднамеренное нарушение нормального хода программы, человеческое раздолбайство и небольшая доля аппаратных особенностей.
Нельзя защитится от всех напастей, но хотя бы очевидные ошибки не стоит допускать. Программа может быть спроектирована так что возникающая ошибка не приводит к фатальной проблеме но писать такие программы повидимому долго и нудно. Проще накидать кучу кода и ловить все баги в продакшене.
Так что я считаю что программы МОГУТ быть написаны без багов только это долго и нудно, а люди ленивые — поэтому имеем что имеем.
программы МОГУТ быть написаны без багов только это долго и нудно, а люди ленивые — поэтому имеем что имеем

А я считаю, что программы могут быть написаны быстро и не нудно и содержать при этом некоторое количество ошибок. Самое главное, чтобы код был написан таким образом, чтобы ошибка легко выявлялась и не приводила ни к каким плачевным последствиям (т.е. чтобы были реализованы надежные откаты, транзакции и т.п.). В этом случае ошибки будут оперативно выявлены и иправлены. У нас, извините, сроки и план. Мы не можем позволить себе не вносить «очень много правок» и не можем позволить себе долго пытаться отладить эти правки на всех мыслимых и немыслимых ситуациях.
При разработке придерживаемся принципов "экстремального программирования".
в хелпе к делфи черным по белому написано что конструкция S[i] небезопасна и оставлена только для обратной совместимости
Не могли бы вы привести цитату про небезопасность и обратную совместимость?
Что-то не могу найти то самое место… давно дело было. Запомнилось и теперь вот.
Кстати анонимные методы очень понравилось использовать в обработчиках. Объявляю их как референсы и вперед. Хочешь в обработчик присваивай обычный метод, а хочешь анонимный. У какой-нибудь кнопки, где всего одна строка кода при нажатии — идеальный вариант. Количества кода в GUI сократилось оч. прилично.
Если потребуется прервать итерации раньше, внутри функции можно возбуждать исключение, а вызов ForEach помещать в блок try.

Это не слишком уж сурово? Выход через исключение — тот еще юмор.
Можно сделать, чтобы анонимная функция имела var-параметр типа aBreak: Boolean = False, который можно задать в True, когда нужно прерваться
Можно и так, но гораздо проще сделать TForEachRef не процедурой, а функцией, и ориентироваться на результат, что собственно и показано в нижних вариантах кода :)
гораздо проще

Надеюсь, это был просто речевой оборот :) Потому что оба варианты по простоте тривиальны до неприличия. А вариант с процедурой удобнее тем, что если параметр не out, а var, то его умолчальное значение будет задано в вызывающем коде (т.е. когда я не хочу прерываться, мне вообще ничего не нужно делать, все сработает само), а в функции результат придется всегда присваивать, что неудобно, а можно например попросту вообще забыть это сделать.
Просты — да, но задачи решают разные. Особенно при использовании функции с var параметром :)
Я говорил не про функцию, а про процедуру. Процедура удобнее, чем функция, т.к. когда «брейкать» не нужно, я избавлен от необходимости присваивать этому параметру значение, т.к. оно будет присваиваться в вызывающем коде. А функции я обязан буду присваивать значение результата каждый раз в своем коде.
Опять не согласен :)
1. Вызов функции приводит к обязательной инициализации параметра Result.
2. Однако — вызов процедуры с Handled флагом (как var параметр) приводит к обязательной инициализации VAR параметра.
Вопрос — так где выгода, не те-же ли яйца, только в профиль? :)
Не до конца понял, что вы подразумевали под «инициализацией». То, что под результат функции будет выделяться память — это да. Но если результату явно не присвоить значение, то даже компилятор вам скажет «Return value of function might be undefined», т.е. функция вернет случайное «нечто». Я хотел избежать только этого (т.е. чисто архитектурно-проектное соображение при проектировании того класса, т.к. так меньше способов накосячить, забыв присвоить результат).
Про EAX'ы и оверхеды я понял, но в моих проектах обычно тормоза совсем в других местах, далеко не в процессоре.
т.е. функция вернет случайное «нечто». Я хотел избежать только этого (т.е. чисто архитектурно-проектное соображение при проектировании того класса, т.к. так меньше способов накосячить, забыв присвоить результат).
Так а сейчас вам что мешает избежать этого? dcc32 -W^NO_RETVAL и шанса на ошибку не останется.
Я не понял, вы мне это предупреждение включить или выключить рекомендуете? Если я его выключу, то шанс на ошибку только увеличится. А если включить, так он включен у меня, но иногда из-за нескольких сотен ворнингов и хинтов в сторонних компонентах в проекте на полтора миллиона строк кода я могу просмотреть этот ворнинг в моем модуле. Вот если бы вы мне посоветовали, как в Delphi настроить, чтобы он не показывал ворнинги в сторонних компонентах (по папке или как-то фильтровать), было бы супер.
Как-то вот так:
{$WARNINGS OFF} «кривой» код {$WARNINGS ON}
Очень «удобно», особенно когда я буду потом обновлять версию этих компонентов, перевносить эту правку в кучу модулей.
Тогда надо избавляться от этих модулей, разработчики которых не устраняют все предупреждения. Модули становятся частью вашего приложения, все неустраненные предупреждения когда-то могут стать причиной ошибок, независимо от того в каких модулях находятся своих или сторонних.
Не знаю, сработает ли, но директивой можно обернуть импорт этих модулей который будет явно под вашим контролем и не изменятся при обновлении сторонних компонент.
директивой можно обернуть импорт этих модулей

К сожалению, Delphi так не умеет.

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

Увы, учитывая слабое Delphi Community хороших (работающий) компонентов далеко не так много, чтобы можно было просто «взять и отказаться». По поводу «разработчики которых не устраняют», говорим спасибо, если исходники этих компонентов хотя бы вообще комплируются в новых версиях Delphi без допиливания напильником и если последний коммит разработчиков в эти компоненты был хотя бы менее года назад. Мы понимаем, что это плохо, но пока считаем нецелесообразным написание и дальнейшее сопровождение собственных самописных компонент криптографии, архивации, доступа к различным SQL серверам, работы с файлами в графических форматах, сложных визуальных контролов.
При -W^NO_RETVAL предупреждение «Return value of function '%s' might be undefined» будет приводить к ошибке компиляции. Того же можно достичь через настройки проекта.
Вернее, если быть точным, -W^^NO_RETVAL т.к. "^" в командной строке трактуется как escape-символ.
Как минимум в Delphi 2009 такая возможность уже была.
Понял, спасибо! Но к сожалению в IDE я что-то такой настройки не нашел :(
Пункт меню Project ⇒ Options, в диалоге настроек закладка Delphi Compiler ⇒ Hints and Warnings.
Дочерними узлами пункта «Output warnings» перечислены warnings и любому из них помимо «True» и «False» можно задать значение «Error».
UFO just landed and posted this here
В Delphi 2009 и XE2 это есть, возможно зависит от редакции (Starter/Professional/Architect).
Не знал про эту возможность! Включил, наслаждаюсь! :) Спасибо!
Ну и последний момент: результат выполнения функции идет через регистр EAX, а BY REF параметр в виде VAR переменной — по сути ссылка (не важно даже куда она ведет, на область в куче, или собственную память, выделенную посредством VirtualAlloc), что, в итоге, дает дополнительный оверхед при ее использвании.
Этот момент желательно учитывать.
Может и проще, но у ForEach концепция не-функции, и поступать так я бы не стал :)
А я бы не согласился :)
ForEach — как любая итеративная функция имеет право на досрочный выход.
За базу возьмем, к примеру, операцию Cut-Paste файлов из папки в папку, посредством ForEach обертки.
К примеру путь одного из копируемых файлов выходит за диапазон MAX_PATH, что делать?
В ОС принято отображать диалог, в котором пользователь имеет полное право отменить столь сомнительную операцию :)
Это потребует дополнительного параметра в заголовке и дополнительной проверки if при проходе списка элементов, но идея, конечно, годная. Более красивое решение, чем try\except :)
Вообще в Делфи принято активно использовать исключения. И если мы в какой-то функции вызываем неизвестный обработчик — мы должны быть готовы, что он может выкинуть исключение.
Но строить логику на исключениях это конечно не самая хорошая идея.
ЗЫ: и кстати, просто как совет — включайте в состав статьи ссылку на исходный код демопримеров. Не удобно все это копировать ручками и перепроверять, гораздо проще работать с уже готовыми исходниками :)
Автор как-то не до конца прочуствовал мощь анонимных методов. Для меня их основное применение — в многопоточных приложениях, чтобы легко передавать значения переменных между потоками (например, значения локальных переменных метода основного потока передать в какой-то другой поток). Все переменные, на которые есть ссылки из анонимного метода, будут «захвачены» компилятором и размещены не на стеке, а в куче, за счет чего обратиться к их значениям из другого потока можно и после того, как поток управления основного потока выйдет из области видимости того метода, в котором эти переменные были объявлены как локальные.
Если кто пользовался библиотекой OmniThreadLibrary, она вся на анонимных методах работает.
Все переменные, на которые есть ссылки из анонимного метода, будут «захвачены» компилятором и размещены не на стеке, а в куче

Кстати — отлично подмечено, причем тут даже не обязательно многопоточность, достаточно это исполнить рекурсивно и изучить, где в памяти будет расположена каждая из таких переменных.
будут «захвачены» компилятором и размещены не на стеке, а в куче, за счет чего обратиться к их значениям из другого потока
А это безопасно?
Странный вопрос :) Это безопасно, например, если к этим данных обращаться только на чтение из других потоков, а если на запись, то чтобы в момент записи их мог писать только один поток и никто не мог их в это время читать. Это то же самое, как если бы эти данные разместить в глобальных переменных, только сами глобальные переменные при этом заводить не требуется, удобство только в этом :)
Т.е. неявные глобальные переменные, получается. Удобство сомнительное, скорее потенциальный источник ошибок.
C Делфи завязал после семерки, некоторые вещи в новинку. Я правильно понимаю, reference to это просто другой способ записи указателя на функцию? Также без контроля ссылочной целостности?
Во-во, закончил на D2007, смотрю код и в шоке. Анонимные функции, куча новых keywords, хелперы и прочее — круто продвинулись. Единственное, что анонимные функции с классическим begin/end выглядят страшно после шарповых брэкетов, к примеру.
reference to — это новая подкапотная магия компилятора. Там все несколько сложнее. Указатель на функцию под капотом оборачивается в интерфейс. Объект, реализующий этот интерфейс тянет с собой весь scope, с которым будет работать функция.

То есть вот у нес есть запись:
var a: Integer;
begin
  DoSome();
  a := 2;
  DoSomeWithCallBack( function : Integer;
    begin
      Result := a;
      WriteLn(a);
    end;
  );
end;

Код выведет число 2.
Под капотом функция превратится в статическую функцию, а так же будет создан объект типа:
IRefToProc = interface
  function Execute: Integer;
end;
TNewObject = class(TInterfacedObject, IRefToProc)
  a : Integer;
  FStaticProcPointer: Pointer;
  function Execute: Integer;
end;
у которого есть заполненное поле a = 2
Это и есть грубо говоря захваченный scope
Автоматически оно начинает работать для методов класса, т.к. этим же механизмом мы можем захватить переменную self.
Так что когда вы объявляете reference to — вы просите компилятор вот так вот обернуть в интерфейсный враппер ваш метод.
Это принципиальное отличие от of object; методов, которые из себя представляют структуру TMethod с self и указателем на метод.
Да кому это уродство нужно? даже в том, в чем делфи сильна — формошлепстве, уже есть более адекватные вещи.
Да и инструментарий плохой, вызывает рвотные позывы. Приходится поддерживать старый код на делфи иногда.
Плохому танцору понятно что мешает…
А с чего вы решили, что я плохой танцор? Делфи — уродство, какое еще нужно поискать, а если вы не смогли освоить что-то по-новее, ну это ваши проблемы…
Инструментарий XE версий последних лет вполне себе хорош. В чем то конечно не дотягивает до Visual Studio, в чем-то наоборот удобнее. А кроме Vusial Studio и Delphi нормальных альтернатив для разработки чисто под Винду как бы и нет. Если знаете, назовите.
Да, наверное соглашусь и когда нужно создать кроссплатформенное GUI приложение, у Qt практически монополия. Но когда кроссплатформенность не требуется, а вот Винду нужно поддерживать более «нэйтивно», мне кажется Qt — это вставление палок самому себе в колеса (хотя это чисто ИМХО). Я сам не разрабатывал на Qt, у него есть дизайнер форм а-ля как в Visual Studio или Delphi? И как у него со сложными GUI-компонентами типа красивых древовидных списков, всяких сложных гридов? Есть они?
да полно там всего.
Еще зыбыли MFC — достойная вещь, под винду, отлично просто.
MFC для Rich GUI Applications по-моему это ад. ) Я имею в виду когда в приложении действительно много разноплановых окон с большим количеством меню, таблиц, списков (ну а-ля какая-нибудь КИС).
Много лет писал на Delphi, потом на Qt перешел. Ну что сказать про GUI. В Qt Designer'е конечно можно сделать почти все что угодно, правда подход на основе лэйаутов после Делфи кажется слишком мудрёным. И форма в дизайнере может выглядеть совсем не так как в ран-тайме.
Что касается «красивых контрольчиков» — особо не встречал. Предполагается что лучше делать просто и нативно для ОС.

По-поводу Qt вообще (и в частности после Delphi) — очень достойный фреймворк, богатый возможностями. Минусы конечно тоже есть. Гораздо больше возможностей в принципе C++ по сравнению с Delphi имеет. Я прям первый год наиграться не мог :)

Но конечно это ни в коем случае не повод Delphi записывать в «отстой». Хороший, мощный в умелых руках инструмент.
Лэйауты Qt даже после WPF «слишком мудреными» кажутся.:)
А мы игру пишем на Delphi и нам плевать на формошлепство и кроссплатформенность. Никаких рвотных позывов, теплота и ламповость.
Единственный минус Delphi, реально мешающий развиваться языку, — отсутствие бесплатных версий.

Даже Visual Studio (давно выпуская Express) выпустила Community-версию.
Visual studio делайтся мелкософтом и им как владельцам ОС выгодно, чтобы под их ОС писали софт. А вот Эмбаркадеро зарабатывает на продаже лицензий к среде разработки.
У них, кстати, есть крайне бюджетная starter. Не бесплатно, согласен, но и не 1000$ уж никак.
Ошибка Embarcadero в том, что они не привлекают новых разработчиков в свою среду.

Новый, начинающий, смотрит и думает: зачем мне покупать Delphi, когда я могу бесплатно учиться в VS?
Не взращивают новое поколение тех, кто будет покупать их продукт.

И продают только в основном старым дельфистам.

Embarcadero должно быть выгодно, чтобы больше писали на Delphi. Поэтому надо делать бесплатную версию для некоммерческого использования и платную для коммерческого.
У них, кстати, есть крайне бюджетная starter. Не бесплатно, согласен, но и не 1000$ уж никак.

Ага. Без компиляторов для командной строки, навигации по коду, рефакторинга, нормальной отладки, баз данных, сорцов VCL, но с запретом на доход больше штуки. Это не смешно даже на фоне VS Express, что уж говорить про VS Community. Для написания мелких нативных штуковин — ну да, сойдёт. Но новые программисты туда не сунутся.
Так хоть бы для обучения (хотя бы для студентов) бесплатную сделали, как JetBrains.

Раньше во времена 2006 Дельфи вроде были Турбо версии, но чего-то потом пропали.
Еще появился appmethod. Правда убогенький такой. И в бесплатной версии может под андроид компилировать( мы же говорим про любые бесплатные версии?).
Вот вопрос: а можно как-нибудь для кнопочки сделать анонимный метод, что-то типа btnTest.OnClick = procedure(Sender: TObject) begin...end;?
Только при условии что в декларации обработчика будет стоять не TNotiFyEvent, а reference to procedure. Но в этом случае можно будет туда и не анонимные тоже присваивать.
Учитывая, что в reference to procedure можно подсовывать как анонимный, так и обычный метод, мне не понятно, почему Embarcadero давно уже не поменяет свои legacy TNotifyEvent на reference to procedure, видимо, им тупо лень
Ну я так понимаю, во-первых, когда в reference to подсовывают обычный метод (не анонимный), то никакой магии не происходит и все работает по-старому, а во-вторых, уж если вы подсунули туда анонимный метод, значит а) «понимаете что делаете» и б) вряд ли обработчик OnClick это узкое место. OnDraw конечно другое дело.
Нет, не работает все по старому. Просто захватывается self. Можно убедится посмотрев в ассемблер. Если вы имели ввиду VCL — то да, там евенты вряд ли узкое место.
Я подумал что вы предлагает вообще отказаться от механизма of object.
Просто захватывается self

Ну ОК, пусть и так, спасибо за разъяснение. Да, я имел в виду только в VCL, просто потому что удобно.
Сейчас провел тест, померил разницу в скорости вызова:
Скрытый текст
type
  TSimple = procedure of Object;
  TAnonymous = reference to procedure;

type
  TMyObj = class
  public
    procedure Simple;
  end;

function CallMeSimple(const aCallback: TSimple): Integer;
var
  i: Integer;
  t: Cardinal;
begin
  t := GetTickCount;
  Result := 1;
  while GetTickCount-t < 1000 do
  begin
    aCallback;
    inc(Result);
  end;
end;

function CallMeAnonymous(const aCallback: TAnonymous): Integer;
var
  i: Integer;
  t: Cardinal;
begin
  t := GetTickCount;
  Result := 1;
  while GetTickCount-t < 1000 do
  begin
    aCallback;
    inc(Result);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  vObj: TMyObj;
begin
  vObj := TMyObj.Create;
  ShowMessage(IntToStr(CallMeSimple(vObj.Simple)));
  ShowMessage(IntToStr(CallMeAnonymous(procedure
  var
    x: Integer;
  begin
    x := Random(1000);
    x := x + 1;
  end)));
end;

{ TMyObj }

procedure TMyObj.Simple;
var
  x: Integer;
begin
  x := Random(1000);
  x := x + 1;
end;


Скрытый текст
Итог: обычный метод успевает за секунду вызваться около 142 миллиона раз, а анонимный — около 127 миллионов раз (усреднил по нескольким запускам). Т.е. разница в среднем в 15 миллионов, что составляет чуть более 10%.
Я для себя делаю вывод: разницу в скорости имеет смысл учитывать, только если реально вызов срабатывает сотни миллионов раз в секунду, во всех же остальных случаях 10% это вообще ничто (по сравнению с какой-нибудь дисковой или сетевой операцией, например).
Да ради одного ReSharper'a можно выбрать Visual Studio, пусть не бесплатного! А делать рефакторинг в Delphi — это просто ад. Так же как и генирить однотипный контент, шаблоны какого — то кода.
Необходимость писать однотипный код намекает на не самую удачную архитектуру.
Вообще забавно. Автор статьи для демонстрации силы лямбда(анонимных методов) использует стандартные функции для обработки коллекций. Собственно так делают во многих языках(C#, Java,C++). Вот только для дельфи выходит фейл. Вот как выглядит MeanAndStdDev в классическом стиле:

procedure MeanAndStdDev;
var
  Data: TArray<Double>;
  V, Sum, Mean, StdDev: Double;
begin 
  Data := [1, 1, 3, 5, 5];    

  Sum := 0;
  for V in Data do Sum := Sum + V;
  Mean := Sum / Length(Data);

  Sum := 0;
  for V in Data do Sum := Sqr(V - Mean);
  StdDev := Sqrt(Sum /  Pred(Length(Data)));

  WriteLn('Mean: ', Mean, ' StdDev: ', StdDev); // => Mean: 3.0 StdDev: 2.0  
end;

ИМХО гораздо читабельнее, чем в статье. Вообще не видно, зачем тут нужны лямбды.
А причина в чрезмерно раздутом синтаксисе. Предположим, что в дельфи был бы C#овских синтаксис лямбд:

Mean = Data.Reduce((a, b) => a + b); // даже не нужно описывать функцию Sum
StdDev = Sqrt(Data.Map(v => Sqrt(v - Mean)) / Pred(Length(Data)));


Получились однострочники. Вот тут видно, что лямбды могут сократить код, может имеет смысл что это за зверь.
опечатки
было: for V in Data do Sum := Sqr(V — Mean);
стало: for V in Data do Sum := Sum + Sqr(V — Mean);

было: StdDev = Sqrt(Data.Map(v => Sqrt(v — Mean)) / Pred(Length(Data)));
стало: StdDev = Sqrt(Data.Map(v => Sqrt(v — Mean)).Reduce((a, b) => a + b) / Pred(Length(Data)));
Вычисление Mean & StdDev приводятся для примера, а именно: использование внешней свободной функции в качестве аргумента Reduce; связь контекста вызывающего кода MeanAndStdDev и вызываемого анонимного метода Map; каскадной цепочки вызовов «Data..Map..Reduce».
Разумеется, всего лаконичнее смотрится вариант использования библиотечной функции Math.MeanAndStdDev ;)
Языки C#, JavaScript, Java имеют свой синтаксис и возможности, однако статья про Delphi, где нет подобных «стандартных функций для обработки коллекций».
Sign up to leave a comment.

Articles