純粋な C で RAII を実装しますか?

標準にはそのような可能性が含まれていないため、これは固有の実装依存です。 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");
}

版画:

before scope
variable (42) goes out of scope
after scope

こちらをご覧ください


RAII を C に持ち込むための 1 つの解決策 (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);
}

定型コードはすべて 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);
  /* ... */
}

注:上記のようなことを可能にするために、P99 などの高度なマクロ フレームワークを利用することをお勧めします。


コンパイラが C99 (またはそのかなりの部分) をサポートしている場合は、次のような可変長配列 (VLA) を使用できます。

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

    // ...
}

メモリが機能する場合、gcc は C99 に追加されるずっと前にこの機能を持っていた/サポートしていました。これは (おおよそ) 以下の単純なケースと同等です:

int f(int x) { 
    int *vla=malloc(sizeof(int) *x);
    /* ... */
    free vla;
}

ただし、ファイルのクローズやデータベース接続など、dtor が実行できるその他の操作は実行できません。