C++で関数内で関数を一度だけ呼ぶ方法

ある関数を呼ぶときに、その関数の中で一度だけ初期化する関数を呼びたい、そんなときにどうするかというお話です。

static bool initialize()
{
    _tprintf(_T("initialize\n"));
    return true;
}

static void func()
{
    // funcを呼び出したときにinitializeを一度だけ呼び出したい!!
    initialize();
}
  1. グローバル変数で制御する
  2. staticローカル変数で制御する
  3. staticローカル変数で戻り値を受け取る

1.グローバル変数で制御する

グローバル変数に初期化したかどうかを判定するフラグを作り制御します。これが一番古典的なやり方でわかり易いかもしれません。しかし、一つの関数の中で収まる制御にもかかわらず変数のスコープがその関数の外にも存在するのがネックです。もし他の人が間違えてこの変数を操作する可能性があります。

static bool g_initialized = false;
static void func()
{
    if (!g_initialized)
    {
        initialize();
        g_initialized = true;
    }
}

2.staticローカル変数で制御する

1のデメリットを解消するために関数内でstaticなローカル変数を使う。staticを付けた場合、この変数は初めて呼び出されたときだけ初期化され、それ以後はこの関数内で存在し続けます。こうしておけばスコープは関数内となり関数の外からはこの変数にアクセスすることはできないので安全です。

static void func()
{
    static bool initialized = false;
    if (!initialized)
    {
        initialize();
        initialized = true;
    }
}

3.staticローカル変数で戻り値を受け取る

2のデメリットとして、一度だけ呼ぶ、ということをしたいがためにコードの行数が6行にも増えてしまいます。これをもう少し簡単に書くことができます。一度だけ呼びたい関数が戻り値を持つときだけ利用できる方法です。これはstaticローカル変数が関数内で一度だけ初期化されることをうまく利用しています。

static void func()
{
    static bool unused = initialize();
}

※unusedとしていますが、この変数を利用しても構いません。



3はすっきり書けて良いのですが戻り値が存在しない関数には適用できません。そこで2を利用するのがよいですが汎用性を高め可読性を上げるためにマクロにするのもひとつの手です。以下のようなマクロを作っておけば可読性が上がるでしょう。

#define CALL_ONCE(src)     \
     do     \
     {     \
          static bool initialized = false;     \
          if (!initialized)     \
          {     \
               src;     \
               initialized = true;     \
          }     \
     }     \
     while (0)

使うのも簡単ですね。

static void func()
{
    CALL_ONCE(initialize());
}