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