在纯C中实现RAII?

eli*_*ner 60 c raii

是否有可能在纯C中实现RAII

我认为不可能以任何理智的方式,但也许可能使用某种肮脏的技巧.重载标准free函数会想到或者可能覆盖堆栈上的返回地址,这样当函数返回时,它会调用一些其他函数以某种方式释放资源?或者也许有一些setjmp/longjmp技巧?

这纯粹是学术上的兴趣,我无意写出这种不可移植和疯狂的代码,但我想知道这是否可能.

Joh*_*itb 76

这是固有的实现依赖,因为标准不包括这种可能性.对于GCC,cleanup当变量超出范围时,该属性会运行一个函数:

#include <stdio.h>

void scoped(int * pvariable) {
    printf("variable (%d) goes out of scope\n", *pvariable);
}

int main(void) {
    printf("before scope\n");
    {
        int watched __attribute__((cleanup (scoped)));
        watched = 42;
    }
    printf("after scope\n");
}
Run Code Online (Sandbox Code Playgroud)

打印:

before scope
variable (42) goes out of scope
after scope
Run Code Online (Sandbox Code Playgroud)

看到这里

  • 这比我想象的还要整齐! (9认同)

Kel*_*yne 11

将RAII引入C(当你没有cleanup())的一个解决方案是使用将执行清理的代码包装你的函数调用.这也可以打包在一个整洁的宏(最后显示).

/* Publicly known method */
void SomeFunction() {
  /* Create raii object, which holds records of object pointers and a
     destruction method for that object (or null if not needed). */
  Raii raii;
  RaiiCreate(&raii);

  /* Call function implementation */
  SomeFunctionImpl(&raii);

  /* This method calls the destruction code for each object. */
  RaiiDestroyAll(&raii);
}

/* Hidden method that carries out implementation. */
void SomeFunctionImpl(Raii *raii) {
  MyStruct *object;
  MyStruct *eventually_destroyed_object;
  int *pretend_value;

  /* Create a MyStruct object, passing the destruction method for
     MyStruct objects. */
  object = RaiiAdd(raii, MyStructCreate(), MyStructDestroy);

  /* Create a MyStruct object (adding it to raii), which will later
     be removed before returning. */
  eventually_destroyed_object = RaiiAdd(raii,
      MyStructCreate(), MyStructDestroy);

  /* Create an int, passing a null destruction method. */
  pretend_value = RaiiAdd(raii, malloc(sizeof(int)), 0);

  /* ... implementation ... */

  /* Destroy object (calling destruction method). */
  RaiiDestroy(raii, eventually_destroyed_object);

  /* or ... */
  RaiiForgetAbout(raii, eventually_destroyed_object);
}
Run Code Online (Sandbox Code Playgroud)

您可以SomeFunction使用宏来表示所有锅炉板代码,因为每次调用都是相同的.

例如:

/* Declares Matrix * MatrixMultiply(Matrix * first, Matrix * second, Network * network) */
RTN_RAII(Matrix *, MatrixMultiply, Matrix *, first, Matrix *, second, Network *, network, {
  Processor *processor = RaiiAdd(raii, ProcessorCreate(), ProcessorDestroy);
  Matrix *result = MatrixCreate();
  processor->multiply(result, first, second);
  return processor;
});

void SomeOtherCode(...) {
  /* ... */
  Matrix * result = MatrixMultiply(first, second, network);
  /* ... */
}
Run Code Online (Sandbox Code Playgroud)

注意:你想要使用像P99这样的高级宏框架来制作类似上面的东西.

  • 必须显式调用方法(“RaiiDestroyAll”)有点违背了 raii 的理念。 (3认同)
  • https://en.cppreference.com/w/cpp/language/raii “此技术的另一个名称是范围限制资源管理 (SBRM),在 RAII 对象的生命周期因范围退出而结束的基本用例之后”。Bjarne Stroustrup 说“RAII 对于这个概念来说是一个不好的名字......一个更好的名字可能是:构造函数获取,析构函数释放”重点是,无论如何,释放都是自动的。关键是你不应该进行清理调用。这就是 RAII 的定义。我听说一些 C 编译器提供了类似的扩展,但 C 本身无法做到这一点。 (2认同)
  • 显然,C无法执行基于自动作用域的清理。从这个问题开始:“我认为这不可能以任何理智的方式出现,但也许有可能使用某种肮脏的把戏”。我提供的是一种解决方法,您可以借助简单的宏在C中进行*托管*清理,而无需手动调用destroy或清理(清理将在宏中完成),但是您将需要注册指针。 (2认同)

Jer*_*fin 9

如果您的编译器支持C99(或者甚至是其中很大一部分),您可以使用可变长度数组(VLA),例如:

int f(int x) { 
    int vla[x];

    // ...
}
Run Code Online (Sandbox Code Playgroud)

如果内存服务,gcc在添加到C99之前已经/支持了这个功能.这(大致)相当于以下简单情况:

int f(int x) { 
    int *vla=malloc(sizeof(int) *x);
    /* ... */
    free vla;
}
Run Code Online (Sandbox Code Playgroud)

但是,它不允许您执行dtor可以执行的任何其他操作,例如关闭文件,数据库连接等.

  • 请注意 1) 堆栈通常比堆受限得多;2)您基本上无法从堆栈溢出中恢复(您将得到一个您无法处理的 SIGSEGV)。失败的 malloc 将返回 nullptr,而失败的 new 将抛出 std::bad_alloc。 (3认同)