Передача дополнительных параметров в указатели функций 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 универсальна, и многие места в коде ссылаются на нее. Для каждой из этих ссылок не имеет смысла обновлять свою функцию, чтобы включить в нее параметры, которые им не нужны.

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

Ответов (9)

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

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

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

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

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

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

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

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

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

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

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

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

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

Если вы хотите использовать 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 .

Я бы предложил использовать массив 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 * и просто отправить массив с завершающим NULL любого типа, который вам нужен.

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

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

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

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

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

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