如何在C和C ++中实现装饰器

时间:2011-01-12 09:48:48

标签: c++ c decorator

我在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. 在返回调用之前显式放置任何语句,因为大多数函数之间都有许多早期返回。或者就此而言,改变功能的任何内部。
  3. 最优雅的方式是什么?

7 个答案:

答案 0 :(得分:16)

使用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()抛出异常的情况下,您仍然不必担心释放锁。在退出函数之前,所有堆栈对象都将被销毁。

答案 1 :(得分:11)

如果你真的想要一个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
}

答案 2 :(得分:3)

像这样定义包装器:

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

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

然后在每个函数中创建一个SemaphoreWrapper实例:

void func1()
{
    SemaphoreWrapper(global_semaphore);


    ...
}

SemaphoreWrapper的构造函数和析构函数将负责锁定/解锁功能。

答案 3 :(得分:1)

您可以编写包装函数,例如:

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

答案 4 :(得分:1)

别。

你不能通过在这里和那里喷出一些锁来从单线程到多线程,并希望最好。

  1. 您确定没有两个共享全局变量的函数吗? C函数因使用静态分配的缓冲区而臭名昭着。

  2. 互斥锁通常不是可重入的。因此,如果您装饰f1f2而另一个调用另一个,则会陷入僵局。当然,您可以使用更昂贵的可重入互斥锁。

  3. 在最好的时候,多线程很难。通常需要理解和调整执行流程。

    我很难想象扔掉几把锁会有效。

    这显然打消了这样一个事实,即如果函数不是用MT制作的,那么它们可能会更慢(使用所有那些互斥操作),因此你不会获得很多好处。

    如果您确实需要信号量,请让客户端锁定它。他应该知道何时锁定,何时不知道。

答案 5 :(得分:0)

装饰器是一种语言特性,它为底层语义提供语法糖。您可以在C ++中获得的基础语义:只需适当地包装函数;你无法得到的语法糖。

更好的选择是创建一个支持您的用例的适当代码设计,例如:由ineritance和decorator pattern。这并不一定意味着类继承 - 模式也可以使用类模板来实现。

至于您的特定用例,已经发布了更好的替代方案。

答案 6 :(得分:0)

在我看来,你想要做的是Aspect Oriented Programming(AOP)。在C和C ++中几乎没有AOP框架,但从我几个月前看到的情况来看,我认为AspectC++项目提供了AOP概念的一个很好的实现。我没有在生产代码中测试它。