Menu
Coddy logo textTech

Singletonパターン

CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 91/104。

Singleton パターンは、プログラム全体を通してクラスのインスタンスが 1 つだけ存在することを保証し、それにアクセスするためのグローバルな窓口を提供します。これは、設定マネージャー、ロガー、データベース接続プールなど、共有すべきリソースに便利です。

重要な考え方は、誰もインスタンスを直接作成できないように constructor を private にし、その後、唯一のインスタンスを返す静的メソッドを提供することです。

#include <iostream>
#include <string>

class Logger {
private:
    std::string logFile;
    
    // プライベートコンストラクタ - 直接のインスタンス化を防ぐ
    Logger() : logFile("app.log") {}
    
    // コピーコンストラクタと代入演算子を削除
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;

public:
    // 単一のインスタンスにアクセスするための静的メソッド
    static Logger& getInstance() {
        static Logger instance;  // 一度だけ作成され、プログラム終了まで存続する
        return instance;
    }
    
    void log(const std::string& message) {
        std::cout << "[LOG] " << message << "\n";
    }
};

int main() {
    Logger::getInstance().log("Application started");
    
    Logger& logger = Logger::getInstance();
    logger.log("Processing data");
}

getInstance() 内の static Logger instance は「マジック静的変数」と呼ばれる C++11 の機能です。これはスレッドセーフで、1 回だけ初期化されることが保証されています。コピーコンストラクターと代入演算子を削除することで、シングルトンのコピーを誰も作成できないようにしています。

Singleton の使用は控えめにしましょう。Singleton はグローバル状態を導入するため、テストが難しくなる可能性があります。複数のインスタンスがあると問題が生じる、本当に共有すべきリソースに最も適しています。

challenge icon

チャレンジ

簡単

ゲームアプリケーションの設定を管理する GameSettings シングルトンを構築しましょう。これは Singleton パターンの最適な使用例です。ゲームのすべての部分からアクセスできる設定マネージャーを1つだけ用意し、全体で一貫した設定を保証します。

コードを3つのファイルに分けて整理します。

  • GameSettings.h:ゲーム設定を管理するシングルトンクラスを定義します。

    GameSettings クラスには、プライベートメンバーとして3つの設定を保持します。difficulty(文字列)、volume(0~100の整数)、fullscreen(ブール値)です。初期値として "Normal"50false を設定します。

    Singleton パターンを正しく実装します。

    • コンストラクターを private にする
    • コピーコンストラクターと代入演算子を Delete する
    • 単一のインスタンスへの参照を返す静的な getInstance() メソッドを提供する

    設定を操作するために、次の public メソッドを追加します。

    • setDifficulty(const std::string& diff):difficulty を更新します
    • setVolume(int vol):volume を更新します
    • setFullscreen(bool fs):fullscreen モードを更新します
    • displaySettings():現在のすべての設定を出力します
  • GameModule.h:シングルトンにアクセスする独立したモジュールを作成します。

    プリセット名(文字列)を受け取る applyGamePreset という名前の関数を定義します。この関数は GameSettings シングルトンにアクセスし、プリセットに基づいて設定します。

    • "casual":difficulty を "Easy"、volume を 70、fullscreen を false に設定します
    • "competitive":difficulty を "Hard"、volume を 30、fullscreen を true に設定します
    • その他のプリセット:デフォルト設定を変更せずに維持します

    これにより、プログラムの異なる部分から同じシングルトンインスタンスにアクセスし、変更できることが示されます。

  • main.cpp:すべてをまとめ、シングルトンの動作を示します。

    2つの入力を読み取ります。

    1. プリセット名(文字列)
    2. カスタム音量レベル(整数)

    まず、シングルトンに対して displaySettings() を呼び出し、初期状態のデフォルト設定を表示します。その後に空行を出力します。

    次に、applyGamePreset 関数を使用してプリセットを適用し、設定をもう一度表示します。その後に空行を出力します。

    最後に、カスタム入力値で volume だけを上書きし、最終的な設定を表示します。

displaySettings() メソッドは、次の正確な形式で出力する必要があります。

Difficulty: [value]
Volume: [value]
Fullscreen: [yes/no]

たとえば、入力が casual85 の場合:

Difficulty: Normal
Volume: 50
Fullscreen: no

Difficulty: Easy
Volume: 70
Fullscreen: no

Difficulty: Easy
Volume: 85
Fullscreen: no

入力が competitive20 の場合:

Difficulty: Normal
Volume: 50
Fullscreen: no

Difficulty: Hard
Volume: 30
Fullscreen: yes

Difficulty: Hard
Volume: 20
Fullscreen: yes

main.cppGameModule.h の両方が同じシングルトンインスタンスにアクセスしていることに注目してください。一方の場所で行った変更は、どこからでも確認できます。これが、ゲーム設定のような共有リソースに Singleton パターンを使用する強みです。

自分で試してみよう

#include <iostream>
#include <string>
#include "GameSettings.h"
#include "GameModule.h"

using namespace std;

int main() {
    // 入力を読み取る
    string presetName;
    int customVolume;
    cin >> presetName;
    cin >> customVolume;
    
    // TODO: GameSettingsのシングルトンインスタンスを取得する
    
    // TODO: 初期のデフォルト設定を表示する
    
    // TODO: 空行を出力する
    
    // TODO: applyGamePreset関数を使用してプリセットを適用する
    
    // TODO: プリセット適用後の設定を表示する
    
    // TODO: 空行を出力する
    
    // TODO: customVolumeで音量を上書きする
    
    // TODO: 最終設定を表示する
    
    return 0;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: C++オンラインコンパイラ