Как реализовать декораторы в C и C++

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

например, у меня есть несколько функций в файле библиотеки C, который при вызове должен заблокировать семафор и перед возвращением элемента управления вызываемому, должен освободить семафор. без замка они имеют следующую структуру:

int f1(int)
{
    ...
    ... 
}

int f2(char*)
{
    ....
}

int f3(blabla)
{
    ....
}

... fn(...)

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

#lockprotected
int f1(int)
{
   ... /* nothing changed over here */
}
#endlockprotected

или что-то вроде

int f1(int)
{
   ... /* nothing changed over here */
}
#lockprotected f1

чего я не хочу:

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

что было бы самым элегантным способом?

7 ответов


используйте RAII (инициализация сбора ресурсов), чтобы определить блокировку мьютекса. Это позволит вам забыть о точке №2, т. е. вам не нужно отслеживать оператор return, чтобы освободить блокировку.

class Lock {
public:
    Lock () { // acquire the semaphore }
    ~Lock () { // release the semaphore }
}

далее создайте объекты этого класса в начале ваших функций, т. е.

int f1 (int) {
    Lock l;
    // forget about release of this lock
    // as ~Lock() will take care of it
}

побочным преимуществом этого является то, что даже в случае исключения из f1(), вам не нужно беспокоиться о снятия блокировки. Все объекты стека уничтожаются до выхода функции.


Если вы действительно хотите решение C, вы можете пойти с макросами, такими как:

#define LOCK   lock( &yourglobalsemaphore )
#define UNLOCK unlock( &yourglobalsemaphore )

#define LOCKED_FUNCTION_ARG1(TRet, FuncName, TArg1, Arg1Name ) \
TRet FuncName( TArg1 Arg1Name ) { \
 LOCK; \
 TRet ret = FuncName##_Locked( Arg1Name ); \
 UNLOCK; \
 return ret \
} \
TRet FuncName##_Locked(TArg1 Arg1Name )

#define LOCKED_FUNCTION_ARG2(TRet FuncName, TArg1, Arg1Name, TArg2, Arg2Name) \
//...etc

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

пример использования:

LOCKED_FUNCTION_ARG1(int, f1, int, myintarg)
{
   //unchanged code here
}

определите оболочку следующим образом:

class SemaphoreWrapper
{
private:
    semaphore &sem;
public
    SemaphoreWrapper(semaphore &s)
    {
        sem = s;
        sem.lock();
    }

    ~SemaphoreWrapper()
    {
        sem.unlock();
    }
}

затем просто создайте экземпляр SemaphoreWrapper внутри каждой функции:

void func1()
{
    SemaphoreWrapper(global_semaphore);


    ...
}

конструктор и деструктор SemaphoreWrapper позаботятся о функциональности блокировки / разблокировки.


нет.

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

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

  2. мьютексы обычно не являются реентерабельными. Поэтому, если вы украшаете f1 и f2 и один вызывает другого, вы будете тупик. Вы можете конечно, используйте более дорогие реентрантные мьютексы.

многопоточность трудно, в лучшем случае. Обычно это требует понимания и корректировки потока выполнения.

мне трудно представить, что бросание нескольких замков вокруг будет работать к лучшему.

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

Если вам действительно нужен ваш семафор, попросите клиента заблокировать его. Он должен знать, когда запирать, а когда нет.


вы можете написать функции обертки, например:

int f1_locked(int x) {
  lock(..);
  int r=f1(x);
  unlock(..);
  return r;
}

С препроцессором вы можете сохранить определенную работу.

редактировать

как кто-то заявил, Было бы лучше переместить реализации во внутренние функции библиотеки и представить обертки пользователям библиотеки, например:

// lib exports the wrapper:
int f1(int x) {
  lock(..);
  int r=f1_unlocked(x);
  unlock(..);
  return r;
}

// for library internal use only:
int f1_unlocked(int x) {
  ...
}

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

void f2_unlocked() {
  ...
  f1_unlocked();
  ...
}

void f2() {
  lock();
  f2_unlocked();
  unlock();
}

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

лучшей альтернативой было бы создать соответствующую конструкцию кода, которая поддерживает ваш прецедент, например, по инерционности и шаблон "декоратор". Это не обязательно означает наследование класса – шаблон также может быть реализовано с помощью шаблонов классов.

Что касается вашего конкретные вариант использования, лучшие альтернативы уже были опубликованы.


Мне кажется, что то, что вы хотите сделать, это Аспектно-Ориентированное Программирование (AOP). В C и c++ есть несколько фреймворков AOP, но из того, что я видел несколько месяцев назад, я думаю, что AspectC++ проект обеспечивает хорошую реализацию концепций AOP. Однако я не тестировал его в производственном коде.