Подтвердить что ты не робот

Существуют ли различия между этими двумя определениями функций более высокого порядка?

Есть ли различия между 4 заявлениями в основном? Я чувствую, что только apply2 (& func) имеет смысл. Однако все 4 возвращают одинаковое значение.

int func(void) 
{
    return 1;
}

int apply1( int f1(void) )
{
    return f1();
}

int apply2( int (*f1) (void) ) 
{
    return f1();
}

int main() 
{
    apply1(func); 
    apply1(&func);
    apply2(func);
    apply2(&func);

    return 0;
}
4b9b3361

Ответ 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.