ТЕХНИКА ОПТИМИЗАЦИИ ПРОГРАММ

       

Функции с аргументами по умолчанию – из Си++ в классический Си


Язык Си++ выгодно отличается от своего предшественника тем, что поддерживает функции с аргументами по умолчанию. Для чего они нужны? Переставим себе такую ситуацию. Пусть у нас имеется активно используемая функция plot(int x, int y, int color), рисующая в заданных координатах точку заданного цвета. Допустим, в один прекрасный момент мы осознаем, что помимо координат и цвета нам жизненно необходим, скажем, атрибут прозрачности. Как быть? Реализовать еще одну функцию, практически повторяющую первую (ау, метод "copy-and-paste")? Не слишком-то удачное решение! Помимо неоправданного увеличения размеров программы возникнет проблема синхронизации обеих функций – при исправлении ошибок в первой функции, их придется "отлавливать" и во второй. (Вообще же, в грамотно спроектированной программе не должно присутствовать хотя бы двух идентичных блоков).

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

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

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


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

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

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

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

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


Впрочем, величина снижения производительности на практике не превышает 0,5%-1% и ей вполне можно пренебречь. Другой минус – загромождение листинга структурами, что затрудняет его понимание. Однако к такому стилю очень быстро привыкаешь, и дискомфорт исчезает сам собой.

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

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

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


Содержание раздела