эмуляция частичного применения функции в c - PullRequest
10 голосов
/ 02 апреля 2011

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

Я могу написать множество небольших функций, вызывая друг друга так:

// example, not actual code
void (*atJoystickLeft)(void); // fixed signature

.. code ..
atJoystickLeft = &func1;
.. code ..

void func1(void) { func2(10, "a string"); } /

Вместо этого я хотел бы:

atJoystickLeft = &func2(10, "a string");

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

Это сделает код как минимум на 20-30% меньше.Мой простой вопрос: есть ли у кого-нибудь хитрость для эмуляции частичного применения функции в C?(C99)

Ответы [ 2 ]

7 голосов
/ 03 апреля 2011

Короткий ответ: нет, C не поддерживает это.

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

// pointer to function taking a va_alist and return an int:
typedef int (*func)(va_alist); 

void invoke(func, ...) {
    va_alist args;
    va_start(args, func);
    func(args);
    va_end(args);
}

Редактировать: Извините, как указал @missingno, я не сделал эту работу так, как планировалось. В действительности это должны быть две функции: одна принимает входные данные и упаковывает их в структуру, а другая - принимает структуру и вызывает предполагаемую функцию.

struct saved_invocation { 
     func f;
     argument_list args;
};

saved_invocation save(func f, ...) { 
    saved_invocation s;
    va_alist args;
    s.f = f;
    va_start(args, f);
    va_make_copy(s.args, args);
    va_end(args);
}

int invoke(saved_invocation const *s) { 
    s->f(s->args);
}

Для va_make_copy вы попадаете на более нестандартные вещи. Это не то же самое, что va_copy - va_copy, как правило, просто хранит указатель на начало аргументов, который остается действительным, пока текущая функция не вернется. Для va_make_copy вам нужно будет сохранить все фактические аргументы, чтобы вы могли получить их позже. Предполагая, что вы использовали структуру argument, которую я обрисовал ниже, вы будете проходить через аргументы, используя va_arg, и использовать malloc (или любой другой), чтобы выделить узел для каждого аргумента, и создать некую динамическую структуру данных ( например, связанный список, динамический массив) для хранения аргументов, пока вы не будете готовы их использовать.

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

[конец редактирования]

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

int function(va_alist args);

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

int adder(va_alist args) { 
    int arg1 = va_arg(args, int);
    int arg2 = va_arg(args, int);

    return arg1 + arg2;
}

У этого есть две очевидные проблемы: во-первых, даже несмотря на то, что нам больше не нужна отдельная оболочка для каждой функции, мы все равно заканчиваем тем, что добавляем дополнительный код в каждую функцию, чтобы позволить ее вызывать через одну оболочку. С точки зрения размера кода это вряд ли будет намного лучше, чем безубыточность, и может легко привести к чистым убыткам.

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

struct argument { 
    enum type {CHAR, SHORT, INT, LONG, UCHAR, USHORT, UINT, ULONG, /* ... */ };
    union data { 
        char char_data;
        short short_data;
        int int_data;
        long long_data;
        /* ... */
    }
}

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

invoke(func, arg1, arg2);

... вы получите что-то вроде:

invoke(func, make_int_arg(arg1), make_long_arg(arg2));

Очевидно, что можно сделать. К сожалению, это все еще не приносит пользы - первоначальная цель сокращения кода почти наверняка была потеряна. Это исключает функцию-обертку - но вместо простой функции с простой оберткой и простым вызовом мы получаем сложную функцию и сложный вызов и небольшую гору дополнительного кода для преобразования значения в наш специальный тип аргумента, и другой для получения значения из аргумента.

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


  1. «Не будет портативным» - это почти преуменьшение - вы действительно не можете сделать это в C - вам просто нужно использовать язык ассемблера, чтобы даже начать.
1 голос
/ 02 апреля 2011

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

Вы, кажется, пришли из фона FP. В C все должно быть сделано вручную, и я рискнул бы сказать, что в этом случае может быть лучше вместо этого попытаться смоделировать вещи в стиле OO: превратить ваши функции в "методы" , заставляя их получать дополнительный аргумент, указатель на структуру «внутренних атрибутов», this или self. (Если вы знаете, Python , это должно быть очень ясно :))

В этом случае, например, func1 может превратиться в нечто подобное:

typedef struct {
     int code;
     char * name;
} Joystick;

Joystick* makeJoystick(int code, char* name){
    //The constructor
    Joystick* j = malloc(sizeof Joystick);
    j->code = code;
    j->name = name;
    return j;
}

void freeJoystick(Joystick* self){
    //Ever noticed how most languages with closures/objects
    // have garbage collection? :)
}

void func1(Joystick* self){
    func2(self->code, self->name);
}

int main(){
     Joystick* joyleft  = make_Joystick(10, "a string");
     Joystick* joyright = make_Joystick(17, "b string");

     func1(joyleft);
     func1(joyright);

     freeJoystick(joyleft);
     freeJoystick(joyright);

     return 0;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...