Как реализовать декораторы в 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

Используйте 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() исключений вам не нужно беспокоиться об освобождении блокировки. Все объекты стека уничтожаются до выхода функции.

Ответ 2

Если вы действительно хотите решение 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
}

Ответ 3

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

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

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

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

void func1()
{
    SemaphoreWrapper(global_semaphore);


    ...
}

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

Ответ 4

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

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();
}

Ответ 5

Не.

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

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

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

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

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

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

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

Ответ 6

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

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

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

Ответ 7

Мне кажется, что вы хотите Aspect Oriented Programming (AOP). В C и С++ существует несколько систем AOP, но из того, что я видел несколько месяцев назад, я думаю, что проект AspectС++ обеспечивает хорошую реализацию AOP концепции. Однако я не тестировал его в коде производства.