Уникальность имён функций: за и против
Почему в Си указатель на функцию такой неудобный
В языке Си тип «указатель на функцию» весьма громоздок и неудобен. Его синтаксис
далёк от краткости и для упрощения пользования такими указателями рекомендуется использовать typedef :
typedef <возвращаемый тип> (*<синоним типа>)(<параметры>);
Без этого костыля было бы трудно. Но почему так громоздко?
Дело в том, что знания одного только адреса функции недостаточно, чтобы её вызвать.
Перед вызовом необходимо передать ей все её параметры. Способов передачи много, основные способы описаны в
соглашении о вызовах.
Важны как порядок помещения в стек или регистры параметров, так и сами параметры и их типы. Есть как прямой порядок помещения параметров
(слева направо, от первого до последнего), так и обратный (справа налево, от последнего к первому, например, функции с переменным числом параметров).
А ещё параметры могут передаваться через регистры, а так же статические области памяти.
От соглашения так же зависит, кто восстанавливает прежнее состояние стека — вызывающая или вызываемая программа.
Так же нужно знать способ возврата результата и его тип.
То есть информация о функции должна быть исчерпывающей, иначе вызов функции невозможен, как невозможен вызов абонента при неполном знании его телефонного номера.
В принципе, знание имени функции достаточно, чтобы компилятор нашёл её описание и вывел из этого описания тип функции.
Но это в Си, потому что в C++ имена функций в общем случае не уникальны, могут быть переопределены.
То есть возможны функции с одинаковыми именами, которые отличаются набором входных параметров.
Это называется перегрузкой (переопределением) имён функций и
операторов.
void sum (char* dest, char* arg1, char* arg2);
int sum (int, int);
Неуникальность имён функций делает невозможным определение её параметров по одному имени.
Когда знания имени функции достаточно для определения её типа
Однако давайте посмотрим на Хаскелл. В нём функции — объекты первого класса.
Функции в Хаскелл запросто передаются в качестве параметров и столь же запросто возвращаются в качестве значений.
Никаких громоздких описаний! Достаточно просто указать имя функции. Может, в нём запрещены разные функции с одинаковыми именами?
Проверяем, пробуем описать одноимённые функции с разным набором параметров — и компилятор действительно выдаёт ошибку.
Вывод: причиной удобства Хаскелла в работе с функциями как объектами первого класса является то, что имена функций в языке уникальны.
В Хаскелле принадлежность функций к объектам первого класса действительно «на уровне».
Как мы знаем, благодаря типизации Хиндли-Милнера компилятор выводит тип переменных из типа инициализирующих эти переменные выражений.
Так же тип переменной-указателя-на-функцию выводится из имени функции и её описания.
|
Будь уникальным, и тебя заметят
|
Плюсы и минусы уникальности имен функций
Тут надо выбрать, что предпочтительнее: то ли удобство работы с функциями как объектами первого класса, то ли важнее сохранить перегрузку имён в стиле C++.
Стоит отметить, что проблема запрета на неуникальность имён не так остра, чтобы делать из этого трагедию.
Возьмём шаблонные функции, реализующие полиморфизм времени компиляции:
// Псевдо-код
T sum<type T> (T arg1, T arg2);
Такие функции на самом деле — не одна функция, а целое семейство.
Функции sum<type T> на самом деле не существует в программе,
но существуют семейство функций, имя которых начинается на sum , а вот «хвостик» <type T>
будет заменён на какой-то конкретный тип: int , float , double и т. д.
Количество версий sum равно количеству типов, употреблённых в качестве параметров: у каждого типа-параметра — своя версия функции.
Эта замена будет выполнена при компиляции.
И если где-то в программе берётся адрес функции sum<type T> , то на самом деле берётся адрес версии функции для конкретного типа.
Например, sum<int> .
Поэтому при вызове последней компилятор уже обладает всей информацией о ней.
Так же следует учитывать тот момент, что имена функций могут быть одинаковыми с именами переменных, если они в одной области видимости. И это создаёт проблемы:
// Псевдо-код
int ID;
int ID (int arg);
ptr = &ID; // тип ptr выводится из ID, но тут двусмысленность:
// это адрес переменной ID или адрес функции ID?
Поэтому надо следить, за тем, чтобы имена функций и переменных в одной области видимости не совпадали.
Подводя итог, можно сделать вывод, что уникальность имён функций даёт нам немало козырей, неплохо бы ими воспользоваться. Это не потребует больших жертв.
Информация о типе функции хранится в типовой переменной.
Хотя на самом деле это не скалярное значение, а некий контейнер, содержащий информацию о функции:
типы параметров, порядок их передачи в функцию, возвращаемое значение и т. п.
Время жизни типовой переменной ограничено периодом компиляции.
Если имена функций уникальны, то явно объявлять тип указателя на функцию не нужно.
Типовая переменная для каждой функции живёт неявно во время компиляции, но, в общем-то,
ничто не мешает узнать значение этой переменной и познакомить с ним разработчика.
Опубликовано: 2022.05.04, последняя правка: 2022.05.20 20:08
Отзывы
✅ 2022/06/14 23:59, Неслучайный читатель #0
С тем, чтобы имена функций были уникальными, могут как-то помочь правила видимости. Даже если функции в разных областях видимости имеют одинаковые имена, то компилятор может однозначно понять, какую именно имели в виду. А если произошло пересечение областей видимости, то имя можно уточнить:область видимости :: имя функции ✅ 2022/06/15 12:05, Бурановский дедушка #1
Аргументы «за» понятны. Не понятно, какие аргументы «против». Объяснения нет.✅ 2022/06/15 13:10, Автор сайта #2
Требование к именам функций не повторяться часто лишает возможности давать одни и те же имена функциям, у которых одинаковый смысл, а аргументы разные — как по типу, так и по количеству. Например:Нарисовать (x, y, дельта x, дельта y, цвет, толщина); Нарисовать (координаты центра окружности, радиус); Нарисовать (координаты начала синусоиды, амплитуда, частота, начальное смещение фазы); Ещё в C++ есть удивительная фишка, когда функция может являться левой частью выражения:func(i) = 999; Эту идею хотелось развить. Допустим, есть пара функций, одна функция что-то читает, а другая пишет:пиксель = читать (x, y) писать (x, y, пиксель) Хотя у функций есть «симметрия» семантике, но в синтаксисе её нет. Для восстановления симметрии было бы логично записывать так:значение = писксель(x, y) пиксель(x + 1, y + 1) = значение Но требование уникальности к именам зарезает эту идею.✅ 2022/06/15 13:12, Gudleifr #3
Не понятно, какие аргументы аргументы «против». Проблема в том, что при программировании методом масштабирования все функции должны называться одинаково. Вот, изобретает человек новый дек под конкретную задачу, с какими-нибудь наворотами, но методы работы с ними берет в основном от старого дека, с теми же названиями. И умиляется — смотрите, как я красиво реализовал те же "push" и "pop". Но читать-то это совершенно невозможно — по тексту совершенно не понятно место этого куска в общей структуре решения задачи. Поэтому, до какого-то уровня все красиво и банально, а чуть выше начинаются "Не_могу_поверить_что_тут_нужна_эта_функция()". И никакие шаблоны и/или пространства имен тут не помогают. Остаются пустопорожние мечты об автоматическом создании кода.✅ 2023/08/09 09:42, veector #4
В рамках своей работы, на Си (где как известно все си-функции по умолчанию глобальны), применяю следующее правило для формирования уникальных имен всех функций, как внутренних, так и интерфейсных. Идея подсмотрена из реального мира, как мы делаем сокращения для имени и отчества, т.е. по принципу ФИО. Но в софте это ФИО ООП объекта. Например.// Стандартизированный доступ к настройкам приложения в древовидном виде, ООП класс. typedef struct { ... } AppCfgTree_t; // ФИО = ACT.
// Создание и удаление объектов в памяти приложения: AppCfgTree_t *ACTCreate(void); void ACTFree(AppCfgTree_t **ThisPtr);
// Чтение значения (адрес результата чтения (динамическая память) запишется по указателю ValuePtr, для него всегда надо делать free()). bool ACTValueRead(AppCfgTree_t *this, char **ValuePtr, char *Path, ...);
// Запись значения. bool ACTValueWrite(AppCfgTree_t *this, char* Value, char *Path, ...); Естественно, стоит потратить немного времени, что бы назвать класс ООП объекта так, что бы было понятно его смысловое назначение, а так же сокращение имени класса было достаточно индивидуальным и благозвучным. Добавить свой отзыв
Написать автору можно на электронную почту mail(аt)compiler.su
|