Singleton パターン | デザインパターン (c++)

Singleton パターン(シングルトン・パターン)とは、オブジェクト指向のコンピュータプログラムにおける、デザインパターンの1つです。GoF (Gang of Four; 4人のギャングたち) によって定義されました。Singleton パターンを使用することで、そのクラスのインスタンスが1つしか生成されないことを保証するデザインパターンです。

 

 

 

1. 静的ローカル変数方式 (Meyers シングルトン)

[概要]

Singleton パターン、の最初のサンプルプログラムを作成してみます。

「Meyers シングルトン」と呼ばれるシンプルなシングルトン型の Logger を紹介します。

主な特徴は以下の通りです。

 

NOTE

Meyers シングルトンとは、Scott Meyers が提唱した「関数内静的変数を使ったシングルトン実装」のことです。簡潔に説明すると以下の通りです。

  • 実装: シングルトンのインスタンスを返す関数内で static 変数を定義し、その参照を返す
  • 特徴: 遅延初期化(最初の呼び出し時に初期化)され、C++11 以降は言語仕様で関数内 static の初期化がスレッドセーフになっているため追加の同期が不要
  • 利点: 実装が非常にシンプルで安全(多重初期化やデータ競合の心配が少ない)、ヘッダや実装の分離が楽
  • 注意点: グローバル状態になるため設計上の影響を受ける(テストや依存注入が難しくなる)、引数付きコンストラクタが使えない、プログラム終了時の破棄順序に依存する問題が残る場合がある

 

 

[環境]

コンパイラ : g++ 13.3.0
OS : Ubuntu 24.04.3 LTS

 

[コード]

POINT

  •  唯一のインスタンスを getInstance メソッド内で宣言 static Logger instance;
  •  コンストラクタ、デストラクタを private とし、外部から new, delete されるのを防ぐ
  • コピー/ムーブを削除してシングルトンを保証

 

["logger.h"]

// logger.h
#pragma once

#include <atomic>           // for std::atomic
#include <mutex>            // for std::mutex
#include <string>           // for std::string

namespace singleton_example {

/**
 * @brief スレッドセーフなシングルトンとして実装されたロガークラスのヘッダ。
 *
 * Meyers シングルトンを用いて遅延初期化を行います。
 */
class Logger {
private:
    mutable std::mutex mutex_;
    std::atomic<std::size_t> counter_{0};

    Logger() = default;
    ~Logger() = default;

public:
    // コピー/ムーブを削除してシングルトンを保証
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;
    Logger(Logger&&) = delete;
    Logger& operator=(Logger&&) = delete;

    /**
     * @brief シングルトン Logger のインスタンスを取得する。
     *
     * @return Logger& シングルトンの Logger インスタンスへの参照。
     */
    static Logger& getInstance();

    /**
     * @brief メッセージをログ出力する。
     *
     * @param message 出力するログメッセージ。
     */
    void log(const std::string& message);

    /**
     * @brief これまでにログ出力したメッセージ数を取得する。
     *
     * @return std::size_t 出力済みメッセージの総数。
     */
    std::size_t getCount() const;
};

} // namespace singleton_example

 

["logger.cpp"]

// logger.cpp
#include "logger.h"

#include <iostream>

namespace singleton_example {

Logger& Logger::getInstance()
{
    static Logger instance;
    return instance;
}

void Logger::log(const std::string& message)
{
    std::lock_guard<std::mutex> lock(mutex_);
    ++counter_;
    std::cout << "[" << counter_ << "] " << message << std::endl;
}

std::size_t Logger::getCount() const
{
    return counter_.load();
}

} // namespace singleton_example

 

["singleton_pattern_01.cpp"]

// singleton_pattern_01.cpp
// C++23 サンプル: スレッドセーフな Meyers スタイルのシングルトン(簡単な使用例)
// プロジェクト規約に従っています: 関数は camelCase、変数は snake_case、公開 API は Doxygen コメントを使用

#include <iostream>
#include <thread>       // for std::thread, std::this_thread::yield
#include <vector>       // for std::vector

#include "logger.h"

using singleton_example::Logger;

int main()
{
    // シングルトンへの同時アクセスを示すために複数スレッドを生成
    const int num_threads = 6;
    const int messages_per_thread = 5;

    std::vector<std::thread> threads;
    threads.reserve(num_threads);

    for (int t = 0; t < num_threads; ++t) {
        threads.emplace_back([t, messages_per_thread]() {
            for (int i = 0; i < messages_per_thread; ++i) {
                Logger::getInstance().log("thread " + std::to_string(t) + " message " + std::to_string(i));
                // 出力が混在する可能性を高めるために少し yield する
                std::this_thread::yield();
            }
        });
    }

    for (auto& th : threads) {
        th.join();
    }

    std::cout << "合計ログ数: " << Logger::getInstance().getCount() << std::endl;
    return 0;
}

 

ビルド方法:

上記3つのソースファイルを同じ場所に保存し、下記コマンドでビルドします。

g++ -std=c++20 -Wall -Wextra -Wpedantic -pthread singleton_pattern_01.cpp logger.cpp -o singleton_pattern_01.out

 

実行方法:

./singleton_pattern_01.out

 

実行結果:

$ ./singleton_pattern_01.out 
[1] thread 0 message 0
[2] thread 0 message 1
[3] thread 1 message 0
[4] thread 1 message 1
[5] thread 1 message 2
[6] thread 1 message 3
[7] thread 0 message 2
[8] thread 0 message 3
[9] thread 3 message 0
[10] thread 4 message 0
[11] thread 4 message 1
[12] thread 4 message 2
[13] thread 4 message 3
[14] thread 4 message 4
[15] thread 0 message 4
[16] thread 2 message 0
[17] thread 2 message 1
[18] thread 2 message 2
[19] thread 2 message 3
[20] thread 2 message 4
[21] thread 3 message 1
[22] thread 3 message 2
[23] thread 3 message 3
[24] thread 3 message 4
[25] thread 1 message 4
[26] thread 5 message 0
[27] thread 5 message 1
[28] thread 5 message 2
[29] thread 5 message 3
[30] thread 5 message 4
合計ログ数: 30
$ 

 

 

2. 静的メンバ変数方式

[概要]

「静的メンバ変数方式」と呼ばれるシングルトン型の Logger を紹介します。

主な特徴は以下の通りです。

 

NOTE

  • 明示的に new で生成
  • スレッドセーフではないので、ロックが必要
  • 破棄タイミングの制御が難しい

 

 

[環境]

コンパイラ : g++ 13.3.0
OS : Ubuntu 24.04.3 LTS

 

 

[コード]

[プログラムソース "logger.h"]

// logger.h
// Logger クラスの宣言(ヘッダ)

#pragma once

#include <atomic>
#include <memory>
#include <mutex>
#include <string>

/**
 * @brief 明示的に破棄できるシングルトン Logger の宣言。
 *
 * 実装は logger.cpp にあり、スレッド安全な初期化と明示破棄をサポートします。
 */
class Logger {
private:
    // メンバー変数(先に記載)
    static std::unique_ptr<Logger> instance_ptr_;
    static std::once_flag init_flag_;

    mutable std::mutex mutex_;
    std::atomic<std::size_t> counter_{0};

    // unique_ptr のデフォルトデリータが private デストラクタを呼べるようにする
    friend struct std::default_delete<Logger>;

    // 特殊メンバはメソッド群の先頭に定義するが実装は cpp に置く
    Logger() = default;
    ~Logger() = default;

public:
    // コピー/ムーブを禁止
    Logger(const Logger&) = delete;
    Logger& operator=(const Logger&) = delete;
    Logger(Logger&&) = delete;
    Logger& operator=(Logger&&) = delete;

    /**
     * @brief シングルトンインスタンスを取得する。
     *
     * 初回呼び出し時にインスタンスを生成します(スレッド安全)。
     */
    static Logger& getInstance();

    /**
     * @brief シングルトンを明示的に破棄する。
     *
     * 注意: 破棄後に他スレッドが Logger を使用していると危険です。
     */
    static void destroyInstance();

    /**
     * @brief メッセージをログ出力する(スレッドセーフ)。
     */
    void log(const std::string& msg);

    /**
     * @brief これまでに出力したメッセージ数を返す。
     */
    std::size_t getCount() const;
};

 

[プログラムソース "logger.cpp"]

// logger.cpp
#include "logger.h"

#include <iostream>
#include <memory>
#include <mutex>
#include <utility>

// 静的メンバーの定義
std::unique_ptr<Logger> Logger::instance_ptr_;
std::once_flag Logger::init_flag_;

Logger& Logger::getInstance()
{
    std::call_once(init_flag_, []() { instance_ptr_.reset(new Logger()); });
    return *instance_ptr_;
}

void Logger::destroyInstance()
{
    instance_ptr_.reset();
}

void Logger::log(const std::string& msg)
{
    std::lock_guard<std::mutex> lock(mutex_);
    ++counter_;
    std::cout << "[" << counter_ << "] " << msg << std::endl;
}

std::size_t Logger::getCount() const
{
    return counter_.load();
}

 

[プログラムソース "singleton_pattern_02.cpp"]

// singleton_pattern_02.cpp
// 明示的に破棄できるシングルトン(std::unique_ptr + std::call_once)
// サンプル実装:スレッド安全にログ出力し、必要なら明示破棄が可能

#include <atomic>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#include <utility>

#include "logger.h"


int main()
{
    const int num_threads = 6;
    const int msgs_per_thread = 5;

    std::vector<std::thread> threads;
    threads.reserve(num_threads);

    for (int t = 0; t < num_threads; ++t) {
        threads.emplace_back([t, msgs_per_thread]() {
            for (int i = 0; i < msgs_per_thread; ++i) {
                Logger::getInstance().log("thread " + std::to_string(t) + " message " + std::to_string(i));
                std::this_thread::yield();
            }
        });
    }

    for (auto& th : threads) {
        th.join();
    }

    std::cout << "Total messages: " << Logger::getInstance().getCount() << std::endl;

    // 明示的に破棄する例(このプログラムでは不要だが動作する)
    Logger::destroyInstance();

    return 0;
}

 

[プログラムソース "Makefile"]

# Makefile for singleton_pattern_02 sample
#
# Targets:
#   all / build - build the executable
#   run         - run the built executable
#   clean       - remove built artifacts
#   help        - show this help

CXX := g++
CXXFLAGS := -std=c++23 -O2 -Wall -Wextra -pthread
LDFLAGS :=

SRC_DIR := .
SRCS := $(SRC_DIR)/logger.cpp $(SRC_DIR)/singleton_pattern_02.cpp
OBJS := $(SRCS:.cpp=.o)
TARGET := $(SRC_DIR)/singleton_pattern_02.out

.PHONY: all build run clean help

all: build

build: $(TARGET)

$(TARGET): $(SRCS)
	$(CXX) $(CXXFLAGS) -o $@ $(SRCS) $(LDFLAGS)

run: $(TARGET)
	@echo "Running $(TARGET)"
	$(TARGET)

clean:
	@echo "Cleaning..."
	-@rm -f $(TARGET) $(OBJS)

help:
	@echo "Makefile for singleton_pattern_02 sample"
	@echo "Available targets:"
	@echo "  make        (or make all)  - build the executable"
	@echo "  make run                   - run the executable (builds first if needed)"
	@echo "  make clean                 - remove build artifacts"
	@echo "  make help                  - show this message"

 

ビルド方法:

make コマンドを実行するだけです

$ make
g++ -std=c++23 -O2 -Wall -Wextra -pthread -o singleton_pattern_02.out ./logger.cpp ./singleton_pattern_02.cpp 
$

 

実行結果:

$ ./singleton_pattern_02.out 
[1] thread 0 message 0
[2] thread 0 message 1
[3] thread 0 message 2
[4] thread 0 message 3
[5] thread 0 message 4
[6] thread 1 message 0
[7] thread 1 message 1
[8] thread 1 message 2
[9] thread 1 message 3
[10] thread 1 message 4
[11] thread 2 message 0
[12] thread 2 message 1
[13] thread 2 message 2
[14] thread 2 message 3
[15] thread 2 message 4
[16] thread 3 message 0
[17] thread 3 message 1
[18] thread 3 message 2
[19] thread 3 message 3
[20] thread 5 message 0
[21] thread 5 message 1
[22] thread 5 message 2
[23] thread 4 message 0
[24] thread 4 message 1
[25] thread 4 message 2
[26] thread 4 message 3
[27] thread 4 message 4
[28] thread 5 message 3
[29] thread 5 message 4
[30] thread 3 message 4
Total messages: 30
$ 

 

 

 

 

 

 

 

 

3. ダブルチェックロッキング方式

[概要]

 

NOTE

  • マルチスレッド環境での安全性を確保
  •  実装が複雑で、C++11 以降でも注意が必要

 

 

[環境]

コンパイラ : g++ 13.3.0
OS : Ubuntu 24.04.3 LTS

 

 

[コード]

[プログラムソース "***.cpp"]

class Singleton {
private:
    static Singleton* instance;
    Singleton() = default;
    Singleton() = default;
public:
    // コピー/ムーブを削除してシングルトンを保証
    Singleton(const Logger&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    Singleton(Singleton&&) = delete;
    Singleton& operator=(Singleton&&) = delete;

    static Singleton* getInstance() {
        if (!instance) {
            std::lock_guard<std::mutex> lock(mtx);
            if (!instance) {
                instance = new Singleton();
            }
        }
        return instance;
    }
};

Singleton* Singleton::instance = nullptr;
std::mutex mtx;

 

 

4. テンプレートベースの汎用シングルトン

[概要]

 

NOTE

  • 汎用的に使える
  •  T によって異なる型のシングルトンを生成可能。

 

 

[環境]

コンパイラ : g++ 13.3.0
OS : Ubuntu 24.04.3 LTS

 

[コード]

[プログラムソース "***.cpp"]

template <typename T>
class Singleton {
public:
    static T& instance() {
        static T instance;
        return instance;
    }
};

 

 

ライセンス

本ページの情報は、特記無い限り下記 MIT ライセンスで提供されます。

The MIT License (MIT)

  Copyright 2025 Kinoshita Hidetoshi

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

 

 

参考

 


 

変更履歴

2025-11-01 - 新規作成

 

Programming Items トップページ

プライバシーポリシー