Передача большего количества параметров в указатели на функции C

Допустим, я создаю шахматную программу. У меня есть функция

void foreachMove( void (*action)(chess_move*), chess_game* game); 

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

chess_move getNextMove(chess_game* game, int depth){
  //for each valid move, determine how good the move is
  foreachMove(moveHandler, game);
}

void moveHandler(chess_move* move){
  //uh oh, now I need the variables "game" and "depth" from the above function
}

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

Как я могу передать дополнительные параметры функции, которую я вызываю через указатель?

14.08.2008 16:51:07
9 ОТВЕТОВ

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

void foreachMove( void (*action)(chess_move*, int), chess_game* game )
8
14.08.2008 16:58:47

Ах, если бы только C поддерживал замыкания ...

Антонио прав; если вам нужно передать дополнительные параметры, вам нужно переопределить указатель на функцию, чтобы принять дополнительные аргументы. Если вы точно не знаете, какие параметры вам понадобятся, у вас есть как минимум три варианта:

  1. Пусть последний аргумент в вашем прототипе будет пустым *. Это дает вам гибкость при передаче чего-либо еще, что вам нужно, но это определенно не безопасно для типов.
  2. Используйте переменные параметры (...). Учитывая мой недостаток опыта работы с переменными параметрами в C, я не уверен, что вы можете использовать это с указателем на функцию, но это дает еще большую гибкость, чем первое решение, хотя все еще с отсутствием безопасности типов.
  3. Обновление до C ++ и использование функциональных объектов .
11
14.08.2008 17:19:28

Если я правильно читаю, я бы предложил, чтобы ваша функция взяла указатель на структуру в качестве аргумента. Затем ваша структура может иметь «игру» и «глубину», когда она им нужна, и просто оставить для них значение 0 или ноль, когда они вам не нужны.

Что происходит в этой функции? У вас есть условие, которое говорит,

if (depth > -1) //some default
  {
  //do something
  }

Всегда ли функция ТРЕБУЕТ «игры» и «глубины»? Тогда они всегда должны быть аргументами, и это может войти в ваши прототипы.

Вы указываете, что функция требует иногда только «игры» и «глубины»? Ну, может сделать две функции и использовать каждую, когда вам нужно.

Но иметь структуру в качестве аргумента, вероятно, проще всего.

3
24.11.2016 22:22:23

Если вы хотите использовать C ++, вы можете использовать «функциональный объект»:

struct MoveHandler {
    chess_game *game;
    int depth;

    MoveHandler(chess_game *g, int d): game(g), depth(d) {}

    void operator () (chess_move*) {
         // now you can use the game and the depth
    }
};

и преврати свой foreachMoveшаблон:

template <typename T>
void foreachMove(T action, chess_game* game);

и вы можете назвать это так:

chess_move getNextMove(chess_game* game, int depth){
    //for each valid move, determine how good the move is
    foreachMove(MoveHandler(game, depth), game);
}

но это не помешает вашему другому использованию MoveHandler.

5
14.08.2008 17:32:18

Я бы предложил использовать массив void *, причем последняя запись всегда void. скажем, вам нужно 3 параметра, вы можете сделать это:

void MoveHandler (void** DataArray)
{
    // data1 is always chess_move
    chess_move data1 = DataArray[0]? (*(chess_move*)DataArray[0]) : NULL; 
    // data2 is always float
    float data1 = DataArray[1]? (*(float*)DataArray[1]) : NULL; 
    // data3 is always char
    char data1 = DataArray[2]? (*(char*)DataArray[2]) : NULL; 
    //etc
}

void foreachMove( void (*action)(void**), chess_game* game);

а потом

chess_move getNextMove(chess_game* game, int depth){
    //for each valid move, determine how good the move is
    void* data[4];
    data[0] = &chess_move;
    float f1;
    char c1;
    data[1] = &f1;
    data[2] = &c1;
    data[3] = NULL;
    foreachMove(moveHandler, game);
}

Если все параметры имеют одинаковый тип, вы можете избежать массива void * и просто отправить массив с нулевым символом в конце любого необходимого вам типа.

1
14.08.2008 18:15:26

+1 к Антонио. Вам нужно изменить объявление указателя функции, чтобы принимать дополнительные параметры.

Кроме того, пожалуйста, не начинайте проходить вокруг пустых указателей или (особенно) массивов пустых указателей. Это просто напрашивается на неприятности. Если вы начнете передавать пустые указатели, вам также придется передать какое-то сообщение, чтобы указать, какой тип указателя (или типы). Эта техника редко подходит.

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

0
14.08.2008 18:39:46

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

http://publications.gbdirect.co.uk/c_book/chapter9/stdarg.html

Просто к сведению, пример кода в ссылке: в некоторых местах они имеют «n args», а в других это «n_args» с подчеркиванием. Все они должны иметь подчеркивание. Я думал, что синтаксис выглядит немного забавно, пока не понял, что в некоторых местах они опустили подчеркивание.

0
14.08.2008 20:22:54

Используйте typedef для указателя функции. Смотрите мой ответ на этот вопрос

0
23.05.2017 11:53:02

Другой вариант - изменить chess_moveструктуру вместо прототипа функции. Структура предположительно определена только в одном месте. Добавьте членов в структуру и заполните структуру соответствующими данными перед любым вызовом, который ее использует.

0
8.04.2013 05:08:22