Ответ 1
Во-первых, указатели на функции трудны. Думая, что вы можете передать функцию в качестве параметра другой функции, требуется некоторый изгиб ума, похожий на понимание рекурсии. Сначала вы не получите его, но потом внезапно это, как потоки понимания, открываются в вашем мозгу, и вы просвещены.
Но тогда вам все же нужно знать правила передачи функций как параметры в C и С++. На этих языках функции не являются первоклассными гражданами, поэтому существует множество ограничений на то, что вы можете с ними делать.
Синтаксис
Синтаксис указателя функции немного уродлив. Основная анатомия [return type] (*[name])([argument list])
. Скобки вокруг *name
необходимы, чтобы устранить помехи между указателем функции и функцией, возвращающей указатель:
// not function pointers: * not grouped to function name
int x(); // function that returns an int
int* x(); // function that returns an int*
int *x(); // also a function that returns an int*, spaces don't matter
// function pointers: * grouped to function name
int (*x)(); // pointer to a function that returns an int
int* (*x)(); // pointer to a function that returns an int*
Распад
В терминах передачи как параметров функции ведут себя примерно так же, как массивы. Когда они передаются, они меняются на указатель. Для сравнения:
void Foo(int bar[4]); // equivalent to: void Foo(int* bar)
void Bar(int baz()); // equivalent to: void Bar(int (*baz)())
Это просто потому, что функции и массивы не назначаются и не копируются:
int foo[4];
int bar[4] = foo; // invalid
int foo();
int bar() = foo; // invalid
Следовательно, единственный способ передать их как функциональные параметры - это передать их адрес, а не копировать их. (Это спорно для массивов, но как это работает.) Тот факт, что эти "ценности" превращаются в указатели, когда передаются в качестве параметров называется "распад".
Эти два прототипа совместимы (то есть они относятся к одной и той же функции, а не к разным перегрузкам), и поэтому нет разницы между ними:
int foo(void bar());
int foo(void (*bar)());
Визуализация в сторону, нет абсолютно никакой разницы между этими двумя объявлениями. Обе функции принимают указатель на функцию, независимо от того, выглядит она так или нет из-за распада. Хотя, поскольку распад часто считается неприятным и запутанным, большинство разработчиков предпочитают явно запрашивать указатель на функцию (и многие разработчики даже не знают, что типы функций могут распадаться).
Неявные преобразования
Теперь о передаче функций в качестве параметров. Это просто следствие распада: функции должны быть неявно преобразованы в их тип указателя функции. Это означает, что вы можете передать функцию, где ожидается указатель функции, и компилятор получит свой адрес для вас. Для этой цели они снова совпадают:
int foo();
int (*bar)() = foo; // the compiler implicitly assigns the address of foo to bar
int (*baz)() = &foo; // you explicitly assign the address of foo to baz
Объедините эти два объяснения, и вы поймете, что ваши четыре вызова функций одинаковы. apply1
и apply2
оба принимают один и тот же параметр (int (*)(void)
), даже если это не очевидно для apply1
; и когда вы вызываете функции с помощью func
вместо &func
, компилятор неявно берет адрес для вас и делает его эквивалентным &func
.
Следующее выходит за рамки вопроса, но в нем подробно изложена предыдущая часть, и я думаю, что это немного опрятно.
Ссылки на функции [Только С++]
Это малоизвестный факт, но также можно передавать ссылки на массивы и функции: в этом случае распад не происходит. Вот так:
void Foo(int (&bar)[4]); // NOT equivalent to void Foo(int* bar)
void Bar(int (&baz)()); // NOT equivalent to void Bar(int (*baz)())
В этом случае вам не разрешается использовать адрес-оператора, потому что не существует неявного преобразования между типами указателей и ссылочными типами. Поражение распада обычно рассматривается как хорошая вещь, поскольку распад часто путается.
int baz();
Bar(baz); // valid
Bar(&baz); // INVALID
Ссылки на функции подчиняются тем же правилам, что и обычные ссылки: они могут быть назначены только во время определения и не могут быть нулевыми.
Определения типов
Вы можете сделать указатели функций менее уродливыми, используя typedef
.
typedef int (*X)();
X func; // func is a pointer to a function that returns an int
Все становится интереснее, если вы выведете часть (*)
:
typedef int X();
X* func; // func is a function pointer
X& func; // func is a function reference [C++ only]
X func; // func is a function declaration (!!)
В последнем случае X func;
эквивалентно заявлению, выражающему int func();
. Не делайте это дома, если вы не хотите путать ад всех.
decltype
имеет значение [только С++]
Еще одно интересное различие между функциями и указателями функций возникает с использованием decltype
. decltype
"возвращает" тип выражения. Для этой конструкции существует разница между function
и &function
:
int bar();
decltype(bar); // type is int ()
decltype(&bar); // type is int (*)()
Это различие особенно важно, если вы хотите передать тип в качестве параметра шаблона, скажем, в std::unique_ptr
.
std::unique_ptr<void, decltype(free)> foo; // INVALID
std::unique_ptr<void, decltype(&free)> foo; // valid
Первое недопустимо, поскольку оно попытается создать функцию как поле экземпляра unique_ptr
.