シングルトンパターン
CoddyのCジャーニー「Object Oriented Programming」セクションの一部 — レッスン 54/61。
プログラム全体を通して、オブジェクトのインスタンスがただ1つだけ必要になることがあります。たとえば、設定マネージャー、ロギングシステム、あるいはデータベース接続などです。Singletonパターンは、インスタンスが1つしか存在しないことを保証し、それに対するグローバルなアクセスポイントを提供します。
C言語では、ソースファイル内のstatic変数を使用してこれを実装します。ファイルスコープにおけるstaticは、その変数がそのファイルに対してプライベートであり、プログラムの生存期間中保持されることを意味することを覚えておいてください。ゲッター関数と組み合わせることで、これがシングルトンを作成します:
// config.c
#include "config.h"
#include <stdlib.h>
struct Config {
int max_connections;
int timeout;
};
static Config* instance = NULL;
Config* get_config(void) {
if (instance == NULL) {
instance = malloc(sizeof(Config));
instance->max_connections = 100;
instance->timeout = 30;
}
return instance;
}魔法は get_config() 内で起こります。最初の呼び出しでインスタンスが作成および初期化されます。それ以降のすべての呼び出しは、同じポインタを返します。
static キーワードは、instance が関数呼び出しの間でその値を保持し、他のファイルから隠されたままになることを保証します。
ヘッダーからは、ユーザーにはゲッター関数のみが見え、静的変数に直接アクセスしたり変更したりすることはできません。get_config() をどこから何度呼び出しても、常に同じオブジェクトが取得されます。
チャレンジ
簡単シングルトンパターンを使用して、アプリケーション全体の構成を管理する Settings モジュールを構築しましょう。コードが設定を何度要求しても、常にまったく同じインスタンスを受け取る必要があります。
コードは以下の3つのファイルに分けて構成します:
settings.h: 前方宣言を使用して不透明なSettings型を宣言します(構造体の本体は隠したままにします)。シングルトンインスタンスへのポインタを返すget_settings関数と、2つの設定値volume(整数)およびbrightness(整数)のゲッターおよびセッター関数を宣言します。ここではインクルードガードが不可欠です。settings.c: ここで実際の処理を記述します。volumeとbrightnessフィールドを持つ実際のSettings構造体を定義します。単一のインスタンスを保持するためのstaticポインタ変数を(NULLで初期化して)作成します。get_settingsを実装し、最初の呼び出しでインスタンスを割り当ててデフォルト値(volume: 50, brightness: 75)で初期化し、それ以降の呼び出しでは既存のインスタンスを単に返すようにします。隠されたフィールドにアクセスして変更するためのゲッターとセッターを実装します。main.c: シングルトンが正しく機能することを実証します。新しい音量と明るさの値を示す2つの整数を読み取ります。get_settingsを2回呼び出し、その結果を2つの異なるポインタ変数に格納します。最初のポインタを使用して、入力された値で音量と明るさを更新します。次に、2番目のポインタを使用して両方の値を取得し、出力します。シングルトンが機能していれば、一方のポインタを介して行われた変更が、もう一方のポインタを介して確認できるはずです。
プログラムは2つの入力を受け取ります:
- 新しい音量の値
- 新しい明るさの値
一方のポインタを介して値を変更し、もう一方のポインタを介して読み取った後、音量と明るさを別々の行に出力してください。
入力が 80 と 100 の場合の出力例:
80
100入力が 25 と 60 の場合の出力例:
25
60入力が 0 と 50 の場合の出力例:
0
50重要なポイントは、両方のポインタがメモリ内の同じオブジェクトを指しているということです。これがシングルトンパターンの本質です。ソースファイル内の静的変数は、インスタンスが1つしか存在しないことを保証し、プログラムの全期間にわたって存続します。
チートシート
Singleton パターンは、プログラム全体を通してオブジェクトのインスタンスが1つだけ存在することを保証し、それに対するグローバルなアクセスポイントを提供します。
C言語では、ソースファイル内の static 変数とゲッター関数を組み合わせて Singleton を実装します。
// config.c
#include "config.h"
#include <stdlib.h>
struct Config {
int max_connections;
int timeout;
};
static Config* instance = NULL;
Config* get_config(void) {
if (instance == NULL) {
instance = malloc(sizeof(Config));
instance->max_connections = 100;
instance->timeout = 30;
}
return instance;
}主なポイント:
- ファイルスコープでの
staticキーワードにより、変数はそのファイル内でのみプライベートとなり、プログラムの実行期間中保持されます。 - ゲッター関数は、最初の呼び出し時にインスタンスが
NULLかどうかを確認し、作成と初期化を行ってから返します。 - それ以降の呼び出しでは、同じポインタを返します。
- ユーザーはヘッダーを介してゲッター関数のみを参照するため、static 変数に直接アクセスしたり変更したりすることはできません。
- ゲッターを何度、どこから呼び出しても、常に同じオブジェクトが取得されます。
自分で試してみよう
#include <stdio.h>
#include "settings.h"
int main() {
int new_volume, new_brightness;
scanf("%d", &new_volume);
scanf("%d", &new_brightness);
// TODO: get_settings を2回呼び出し、2つの異なるポインタ変数に格納する
// TODO: 1つ目のポインタを使用して、volume と brightness を入力値で更新する
// TODO: 2つ目のポインタを使用して、volume と brightness を取得し、表示する
// (これは、両方のポインタが同じシングルトンインスタンスを指していることを示しています)
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。