An Embedded Engineer’s Blog

とある組み込みエンジニアの備忘録的なブログです。

【GitHub Copilotと作る Pythonで OpenGL 3Dプログラミング】 - 第3回「imguiを組み込む」

GitHub Copilotと作る PythonOpenGL 3Dプログラミング

第3回「imguiを組み込む」

はじめに

前回は、GLFWを使ってOpenGLウィンドウを作成しました。

今回は、imgui(Dear ImGui)を組み込んで、リアルタイムにパラメータを調整できるデバッグUIを追加します。imguiを使うことで、背景色の変更やFPSの表示など、インタラクティブな操作が可能になります。

imguiとは

Dear ImGuiは、ゲームやツール開発で広く使われている即時モード(Immediate Mode)のGUIライブラリです。

特徴: - 軽量・高速: 描画のオーバーヘッドが小さい - 即時モード: 状態管理が不要で、毎フレームUIを記述する - デバッグ向け: パラメータ調整やデバッグ情報の表示に最適 - カスタマイズ性: テーマやスタイルを自由に変更可能

Pythonではimgui-bundleパッケージを使用します。これはDear ImGuiのPythonバインディングで、OpenGLやGLFWとの統合機能も含まれています。

imguiの基本構造

imguiを使ったプログラムは、以下の流れで動作します:

  1. 初期化: imguiコンテキストとレンダラーの作成
  2. フレーム開始: 新しいフレームの開始を宣言
  3. UI描画: ウィジェット(ボタン、スライダーなど)を配置
  4. レンダリング: UIを実際に描画
  5. 終了処理: リソースの解放

サンプルコード

src/main.pyを以下の内容に更新します:

"""
PythonOpenGL - Phase 3: imgui基礎
"""
# imgui_bundleをglfwより先にインポート(GLFWライブラリの重複警告を回避)
from imgui_bundle import imgui
from imgui_bundle.python_backends.glfw_backend import GlfwRenderer

import glfw
from OpenGL.GL import (
    glClear,
    glClearColor,
    glViewport,
    GL_COLOR_BUFFER_BIT,
)


def main() -> None:
    """メイン関数"""
    # GLFWの初期化
    if not glfw.init():
        raise RuntimeError("GLFWの初期化に失敗しました")

    try:
        # OpenGLバージョンの指定(3.3 Core Profile)
        glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
        glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
        glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
        glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)  # macOSで必要

        # ウィンドウの作成
        window = glfw.create_window(800, 600, "PythonOpenGL - imgui", None, None)
        if not window:
            raise RuntimeError("ウィンドウの作成に失敗しました")

        # OpenGLコンテキストを現在のスレッドに設定
        glfw.make_context_current(window)

        # imguiの初期化
        imgui.create_context()
        impl = GlfwRenderer(window)

        # 背景色(RGBA、0.0〜1.0)
        clear_color = [0.2, 0.2, 0.2, 1.0]

        # メインループ
        while not glfw.window_should_close(window):
            # イベントの処理
            glfw.poll_events()
            impl.process_inputs()

            # imguiフレーム開始
            imgui.new_frame()

            # ===== imguiウィンドウ =====
            imgui.begin("Settings")

            # 背景色の変更
            changed, clear_color = imgui.color_edit4("Background", clear_color)

            # FPSの表示
            imgui.text(f"FPS: {imgui.get_io().framerate:.1f}")

            # ボタンの例
            if imgui.button("Reset Color"):
                clear_color = [0.2, 0.2, 0.2, 1.0]

            imgui.end()
            # ===========================

            # 背景色の適用
            glClearColor(*clear_color)

            # 画面のクリア
            glClear(GL_COLOR_BUFFER_BIT)

            # imguiのレンダリング
            imgui.render()
            impl.render(imgui.get_draw_data())

            # バッファの入れ替え
            glfw.swap_buffers(window)

        # imguiの終了処理
        impl.shutdown()

    finally:
        # GLFWの終了処理
        glfw.terminate()


if __name__ == '__main__':
    main()

コードの解説

1. imguiの初期化

imgui.create_context()
impl = GlfwRenderer(window)

imgui.create_context()でimguiのコンテキストを作成し、GlfwRendererでGLFW+OpenGLとの統合を行います。

2. フレーム開始

impl.process_inputs()
imgui.new_frame()

process_inputs()でマウス・キーボード入力をimguiに渡し、new_frame()で新しいフレームを開始します。

3. ウィンドウとウィジェット

imgui.begin("Settings")

changed, clear_color = imgui.color_edit4("Background", clear_color)
imgui.text(f"FPS: {imgui.get_io().framerate:.1f}")

if imgui.button("Reset Color"):
    clear_color = [0.2, 0.2, 0.2, 1.0]

imgui.end()
  • begin() / end(): ウィンドウの開始と終了
  • color_edit4(): RGBA色を編集するカラーピッカー
  • text(): テキストの表示
  • button(): クリック可能なボタン

color_edit4()(変更されたか, 新しい値)のタプルを返します。

4. レンダリング

imgui.render()
impl.render(imgui.get_draw_data())

render()でUI描画データを生成し、impl.render()で実際にOpenGLで描画します。

5. 終了処理

impl.shutdown()

imguiのリソースを解放します。

動作確認

コードを実行してみましょう:

# 仮想環境を有効化
source .venv/bin/activate  # macOS/Linux

# 実行
python src/main.py

「Settings」ウィンドウが表示され、以下の操作ができます:

  • Background: カラーピッカーで背景色を変更
  • FPS: 現在のフレームレートを表示
  • Reset Color: ボタンで背景色を初期値に戻す

imgui Settingsウィンドウ
imgui Settingsウィンドウ

カラーピッカーで背景色を変更すると、リアルタイムに反映されます:

背景色を変更した様子
背景色を変更した様子

よく使うimguiウィジェット

imguiには様々なウィジェットが用意されています。以下はよく使うものです:

# テキスト
imgui.text("Hello, imgui!")
imgui.text_colored(ImVec4(1.0, 0.0, 0.0, 1.0), "Red text")

# ボタン
if imgui.button("Click"):
    print("Button pressed")

# チェックボックス
changed, checked = imgui.checkbox("Enabled", checked)

# スライダー
changed, value = imgui.slider_float("Value", value, 0.0, 1.0)
changed, int_value = imgui.slider_int("Integer", int_value, 0, 100)

# 入力フィールド
changed, text = imgui.input_text("Name", text)

# コンボボックス(ドロップダウン)
items = ["Item 1", "Item 2", "Item 3"]
changed, current = imgui.combo("Select", current, items)

デバッグ情報の表示

imguiはデバッグ情報の表示にも便利です:

imgui.begin("Debug Info")
imgui.text(f"FPS: {imgui.get_io().framerate:.1f}")
imgui.text(f"Mouse: {imgui.get_io().mouse_pos.x:.0f}, {imgui.get_io().mouse_pos.y:.0f}")
imgui.text(f"Window: {imgui.get_io().display_size.x:.0f} x {imgui.get_io().display_size.y:.0f}")
imgui.end()

まとめ

今回は、imguiを組み込んでデバッグUIを追加しました。

  • imguiの初期化と終了処理
  • ウィンドウとウィジェットの配置
  • カラーピッカーで背景色をリアルタイム変更
  • FPSの表示

imguiを使うことで、パラメータの調整やデバッグ情報の表示が簡単にできるようになりました。今後のPhaseでも、imguiを活用してインタラクティブなデモを作成していきます。

次回は、シェーダーの基礎を学び、GPUプログラミングの世界に入っていきます。


前回: 第2回「GLFWでウィンドウを作る」

次回: 第4回「シェーダー入門 - 虹色の三角形を描く」

【GitHub Copilotと作る Pythonで OpenGL 3Dプログラミング】 - 第2回「GLFWでウィンドウを作る」

GitHub Copilotと作る PythonOpenGL 3Dプログラミング

第2回「GLFWでウィンドウを作る」

はじめに

前回は、Pythonで3Dプログラミングを行うための開発環境を構築しました。

今回は、いよいよOpenGLプログラミングの第一歩として、GLFWを使ってウィンドウを表示してみます。ウィンドウの作成は、3Dグラフィックスを表示するための基盤となる重要なステップです。

GLFWとは

GLFW(Graphics Library Framework)は、OpenGLアプリケーションの開発を支援するライブラリです。以下の機能を提供します:

  • ウィンドウの作成と管理
  • OpenGLコンテキストの作成
  • キーボード・マウス入力の処理
  • ジョイスティック対応
  • マルチモニター対応

GLFWは軽量で使いやすく、クロスプラットフォームWindowsmacOSLinux)で動作します。

最小限のウィンドウ表示

まずは、最小限のコードでウィンドウを表示してみましょう。

src/main.pyを以下の内容に更新します:

"""
PythonOpenGL - Phase 2: GLFWウィンドウ作成
"""
import glfw
from OpenGL.GL import (
    glClear,
    glClearColor,
    GL_COLOR_BUFFER_BIT,
)


def main() -> None:
    """メイン関数"""
    # GLFWの初期化
    if not glfw.init():
        raise RuntimeError("GLFWの初期化に失敗しました")

    try:
        # ウィンドウの作成
        window = glfw.create_window(800, 600, "PythonOpenGL", None, None)
        if not window:
            raise RuntimeError("ウィンドウの作成に失敗しました")

        # OpenGLコンテキストを現在のスレッドに設定
        glfw.make_context_current(window)

        # 背景色の設定(ダークグレー)
        glClearColor(0.2, 0.2, 0.2, 1.0)

        # メインループ
        while not glfw.window_should_close(window):
            # 画面のクリア
            glClear(GL_COLOR_BUFFER_BIT)

            # バッファの入れ替え(ダブルバッファリング)
            glfw.swap_buffers(window)

            # イベントの処理
            glfw.poll_events()

    finally:
        # GLFWの終了処理
        glfw.terminate()


if __name__ == '__main__':
    main()

コードの解説

1. GLFWの初期化

if not glfw.init():
    raise RuntimeError("GLFWの初期化に失敗しました")

glfw.init()でGLFWライブラリを初期化します。失敗した場合はFalseが返されるので、エラー処理を行います。

2. ウィンドウの作成

window = glfw.create_window(800, 600, "PythonOpenGL", None, None)

glfw.create_window()でウィンドウを作成します。引数は以下の通りです:

引数 説明
800 ウィンドウの幅(ピクセル
600 ウィンドウの高さ(ピクセル
"PythonOpenGL" ウィンドウのタイトル
None フルスクリーン時のモニター(Noneで通常ウィンドウ)
None コンテキストを共有するウィンドウ(Noneで共有なし)

3. OpenGLコンテキストの設定

glfw.make_context_current(window)

OpenGLの描画命令は「コンテキスト」と呼ばれる状態に対して行われます。この行で、作成したウィンドウのOpenGLコンテキストを現在のスレッドに紐づけます。

4. 背景色の設定

glClearColor(0.2, 0.2, 0.2, 1.0)

glClearColor()で画面をクリアする際の色を設定します。引数はRGBA(赤、緑、青、透明度)で、それぞれ0.0〜1.0の範囲で指定します。ここではダークグレー(0.2, 0.2, 0.2)を設定しています。

5. メインループ

while not glfw.window_should_close(window):
    glClear(GL_COLOR_BUFFER_BIT)
    glfw.swap_buffers(window)
    glfw.poll_events()

3Dアプリケーションの心臓部となるメインループです。ウィンドウが閉じられるまで以下を繰り返します:

  1. glClear(): 画面を背景色でクリア
  2. swap_buffers(): バッファを入れ替えて描画結果を表示(ダブルバッファリング)
  3. poll_events(): キーボード・マウスなどのイベントを処理

6. 終了処理

finally:
    glfw.terminate()

try-finallyを使って、エラーが発生しても必ずglfw.terminate()が呼ばれるようにしています。これにより、GLFWが確保したリソースが適切に解放されます。

動作確認

コードを実行してみましょう:

# 仮想環境を有効化
source .venv/bin/activate  # macOS/Linux
# .venv\Scripts\activate   # Windows

# 実行
python src/main.py

ダークグレーの背景を持つウィンドウが表示されれば成功です!

GLFWウィンドウ
GLFWウィンドウ

ウィンドウを閉じるには、ウィンドウの×ボタンをクリックするか、Escキーを押して...と言いたいところですが、まだキーボード入力を処理していないので×ボタンでのみ閉じられます。

キーボード入力の追加

ウィンドウをEscキーで閉じられるようにしましょう。また、ウィンドウサイズの変更にも対応します。

"""
PythonOpenGL - Phase 2: GLFWウィンドウ作成(キーボード対応版)
"""
import glfw
from OpenGL.GL import (
    glClear,
    glClearColor,
    glViewport,
    GL_COLOR_BUFFER_BIT,
)


def key_callback(window, key: int, scancode: int, action: int, mods: int) -> None:
    """キーボード入力のコールバック関数"""
    if key == glfw.KEY_ESCAPE and action == glfw.PRESS:
        glfw.set_window_should_close(window, True)


def framebuffer_size_callback(window, width: int, height: int) -> None:
    """フレームバッファサイズ変更時のコールバック関数"""
    glViewport(0, 0, width, height)


def main() -> None:
    """メイン関数"""
    # GLFWの初期化
    if not glfw.init():
        raise RuntimeError("GLFWの初期化に失敗しました")

    try:
        # OpenGLバージョンの指定(3.3 Core Profile)
        glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
        glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
        glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
        glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)  # macOSで必要

        # ウィンドウの作成
        window = glfw.create_window(800, 600, "PythonOpenGL", None, None)
        if not window:
            raise RuntimeError("ウィンドウの作成に失敗しました")

        # OpenGLコンテキストを現在のスレッドに設定
        glfw.make_context_current(window)

        # コールバック関数の設定
        glfw.set_key_callback(window, key_callback)
        glfw.set_framebuffer_size_callback(window, framebuffer_size_callback)

        # 背景色の設定(ダークグレー)
        glClearColor(0.2, 0.2, 0.2, 1.0)

        # メインループ
        while not glfw.window_should_close(window):
            # 画面のクリア
            glClear(GL_COLOR_BUFFER_BIT)

            # バッファの入れ替え
            glfw.swap_buffers(window)

            # イベントの処理
            glfw.poll_events()

    finally:
        # GLFWの終了処理
        glfw.terminate()


if __name__ == '__main__':
    main()

追加したコードの解説

OpenGLバージョンの指定

glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)

window_hint()でウィンドウ作成時のオプションを設定します。ここでは以下を指定しています:

  • OpenGL 3.3: モダンなOpenGLの基本となるバージョン
  • Core Profile: 古い非推奨機能を使わないモード
  • Forward Compatible: macOSで必要なオプション

キーボードコールバック

def key_callback(window, key: int, scancode: int, action: int, mods: int) -> None:
    if key == glfw.KEY_ESCAPE and action == glfw.PRESS:
        glfw.set_window_should_close(window, True)

キーが押されたときに呼ばれる関数です。Escキーが押されたらウィンドウを閉じるフラグを立てます。

フレームバッファサイズコールバック

def framebuffer_size_callback(window, width: int, height: int) -> None:
    glViewport(0, 0, width, height)

ウィンドウサイズが変更されたときに呼ばれます。glViewport()で描画領域を新しいサイズに合わせて更新します。

背景色を変えてみよう

せっかくなので、背景色を変えて遊んでみましょう。glClearColor()の引数を変更すると、様々な色の背景を表示できます。

# 例: 青みがかったグレー
glClearColor(0.1, 0.1, 0.2, 1.0)

# 例: 暗い緑
glClearColor(0.0, 0.2, 0.1, 1.0)

# 例: コーンフラワーブルー(DirectXのデフォルト背景色)
glClearColor(0.392, 0.584, 0.929, 1.0)

次回、imguiを導入すると、スライダーでリアルタイムに色を変更できるようになります!

まとめ

今回は、GLFWを使ってOpenGLウィンドウを作成しました。

  • GLFWの初期化と終了処理
  • ウィンドウの作成
  • OpenGLコンテキストの設定
  • メインループの実装
  • キーボード入力のコールバック
  • ウィンドウサイズ変更への対応

次回は、imguiを導入して、パラメータをリアルタイムに調整できるデバッグUIを追加します。


前回: 第1回「開発環境を整えよう」

次回: 第3回「imguiを組み込む」

【GitHub Copilotと作る Pythonで OpenGL 3Dプログラミング】 - 第1回「開発環境を整えよう」

GitHub Copilotと作る PythonOpenGL 3Dプログラミング

第1回「開発環境を整えよう」

はじめに

前回は、このシリーズの概要とGitHub Copilotを活用した開発スタイルについて紹介しました。

今回から、実際の開発に入っていきます。まずは、Pythonで3Dプログラミングを行うための開発環境を構築しましょう。

必要なもの

このシリーズを進めるにあたって、以下のものが必要です:

開発環境の構築

1. プロジェクトディレクトリの作成

まず、プロジェクト用のディレクトリを作成します。

mkdir PythonOpenGL
cd PythonOpenGL

2. Python仮想環境の作成

プロジェクト専用のPython仮想環境を作成します。これにより、他のプロジェクトとパッケージのバージョンが競合することを防げます。

# 仮想環境の作成
python3 -m venv .venv

# 仮想環境の有効化(macOS/Linux)
source .venv/bin/activate

# 仮想環境の有効化(Windows)
# .venv\Scripts\activate

仮想環境が有効になると、ターミナルのプロンプトに(.venv)と表示されます。

3. 必要なパッケージのインストール

3Dプログラミングに必要なパッケージをインストールします。

pip install glfw PyOpenGL numpy imgui-bundle

各パッケージの役割:

パッケージ 役割
glfw ウィンドウの作成、キーボード・マウス入力の処理
PyOpenGL OpenGLPythonバインディング(3D描画API
numpy 数値計算ライブラリ(行列計算、頂点データ処理)
imgui-bundle デバッグUI、パラメータ調整用のGUIライブラリ

4. インストールの確認

パッケージが正しくインストールされたか確認しましょう。

pip list | grep -E "glfw|PyOpenGL|numpy|imgui"

以下のような出力が表示されれば成功です(バージョンは異なる場合があります):

glfw              2.10.0
imgui-bundle      1.92.5
numpy             2.3.5
PyOpenGL          3.1.10

プロジェクト構成

今後、以下のようなディレクトリ構成でプロジェクトを進めていきます:

PythonOpenGL/
├── .venv/            # Python仮想環境(Gitには含めない)
├── src/
│   └── main.py       # メインプログラム
└── README.md         # プロジェクトの説明

srcディレクトリの作成

ソースコードを格納するディレクトリを作成します。

mkdir src

動作確認用のコード

環境が正しくセットアップされたか確認するため、簡単なテストコードを作成しましょう。

src/main.pyを以下の内容で作成します:

"""
PythonOpenGL - 環境構築確認用コード
"""
import sys


def check_packages() -> bool:
    """必要なパッケージがインストールされているか確認する"""
    packages = {
        'glfw': 'glfw',
        'OpenGL': 'PyOpenGL',
        'numpy': 'numpy',
        'imgui_bundle': 'imgui-bundle',
    }

    all_ok = True
    print("=== パッケージ確認 ===")

    for module_name, package_name in packages.items():
        try:
            module = __import__(module_name)
            version = getattr(module, '__version__', 'unknown')
            print(f"✓ {package_name}: {version}")
        except ImportError:
            print(f"✗ {package_name}: インストールされていません")
            all_ok = False

    return all_ok


def main() -> None:
    """メイン関数"""
    print("PythonOpenGL 環境構築確認\n")

    if check_packages():
        print("\n全てのパッケージが正しくインストールされています!")
        print("次回から、実際のOpenGLプログラミングを始めましょう。")
        sys.exit(0)
    else:
        print("\n一部のパッケージがインストールされていません。")
        print("pip install glfw PyOpenGL numpy imgui-bundle を実行してください。")
        sys.exit(1)


if __name__ == '__main__':
    main()

動作確認

作成したコードを実行して、環境が正しくセットアップされているか確認します。

# 仮想環境が有効になっていることを確認(プロンプトに(.venv)が表示されているか)
# 有効になっていない場合は以下を実行
source .venv/bin/activate  # macOS/Linux
# .venv\Scripts\activate   # Windows

# 確認コードを実行
python src/main.py

以下のような出力が表示されれば、環境構築は完了です:

PythonOpenGL 環境構築確認

=== パッケージ確認 ===
✓ glfw: 2.10.0
✓ PyOpenGL: 3.1.10
✓ numpy: 2.3.5
✓ imgui-bundle: 1.92.5

全てのパッケージが正しくインストールされています!
次回から、実際のOpenGLプログラミングを始めましょう。

VS Codeの設定(推奨)

VS Codeを使用している場合、以下の拡張機能をインストールすることをお勧めします:

  • Python: Python言語サポート
  • Pylance: 高速な型チェックとインテリセンス
  • GitHub Copilot: AIコーディング支援

また、ワークスペースPython仮想環境を認識させるため、VS Codeでプロジェクトフォルダを開いた後、コマンドパレット(Cmd+Shift+P / Ctrl+Shift+P)から「Python: Select Interpreter」を選択し、.venv内のPythonを選択してください。

まとめ

今回は、Pythonで3Dプログラミングを行うための開発環境を構築しました。

  • Python仮想環境の作成
  • 必要なパッケージ(glfw, PyOpenGL, numpy, imgui-bundle)のインストール
  • 動作確認用コードの実行

次回は、GLFWを使って実際にウィンドウを表示してみます。いよいよOpenGLプログラミングの第一歩です!


前回: 第0回「このシリーズについて」

次回: 第2回「GLFWでウィンドウを作る」

【GitHub Copilotと作る Pythonで OpenGL 3Dプログラミング 】- はじめに

GitHub Copilotと作る PythonOpenGL 3Dプログラミング

第0回「このシリーズについて」

はじめに

このブログシリーズでは、PythonOpenGLを使った3Dプログラミングを、ゼロからステップバイステップで学んでいきます。

そして、このシリーズには一つ大きな特徴があります。GitHub Copilotをフル活用して開発を進めていくということです。

このシリーズの趣旨

3Dプログラミングは、ゲーム開発、シミュレーション、データ可視化など、さまざまな分野で活用されています。しかし、OpenGLを使った3Dプログラミングは、初学者にとってハードルが高いと感じられることも多いでしょう。

このシリーズでは、以下の点を重視して解説を進めていきます:

  • 段階的な学習: 簡単なウィンドウ表示から始め、徐々に複雑な3D描画へと進みます
  • 実践的なコード: 各回で動作するサンプルコードを提供します
  • インタラクティブなデモ: imguiを使って、パラメータをリアルタイムで調整できるようにします

GitHub Copilotを活用した開発

本シリーズでは、コードの作成にGitHub Copilotを積極的に活用しています。

GitHub Copilotは、AIを活用したコーディング支援ツールです。コードの補完だけでなく、以下のような場面で活用しています:

  • コードの生成: 関数やクラスの実装をCopilotに提案してもらう
  • リファクタリング: より良いコード構造への改善提案
  • ドキュメント作成: コメントやドキュメントの生成
  • デバッグ支援: エラーの原因特定と修正案の提示

実は、このブログ記事自体もGitHub Copilotがメインで執筆しています。筆者がテーマや方向性を指示し、Copilotが文章を生成、筆者がレビュー・修正するという流れで作成しています。

AIと人間が協力してコードと記事を作り上げていく様子も、このシリーズの見どころの一つです。

対象読者

このシリーズは、以下のような方を対象としています:

  • Pythonの基本的な文法を理解している方
  • 3Dプログラミングに興味がある方
  • OpenGLを学んでみたいが、どこから始めればいいかわからない方
  • GitHub Copilotを使った開発に興味がある方

3Dプログラミングやグラフィックスの事前知識は必要ありません。必要な概念はその都度解説していきます。

使用する技術

技術 用途
Python 3.x プログラミング言語
GLFW ウィンドウ管理・入力処理
PyOpenGL OpenGLPythonバインディング
NumPy 行列計算・頂点データ処理
imgui-bundle デバッグUI・パラメータ調整
GitHub Copilot AIコーディング支援

シリーズ全体の流れ

このシリーズは、以下のような流れで進めていきます:

  1. 環境構築 - 開発環境のセットアップ
  2. ウィンドウ作成 - GLFWでウィンドウを表示
  3. imgui導入 - デバッグUIの組み込み
  4. シェーダー入門 - GPUプログラミングの基礎
  5. ポリゴン描画 - 初めての3D描画
  6. カメラと座標変換 - 3D空間の操作
  7. 形状描画 - 様々な3Dオブジェクトの描画
  8. 高速化 - バッチ描画とインスタンシング
  9. テクスチャ - 画像の貼り付け
  10. ライティング - 光と影の表現
  11. 応用 - より高度なテクニック

各回で作成したコードは、次の回で拡張していく形式を取ります。最終的には、インタラクティブな3Dビューアが完成する予定です。

公開済み記事一覧

タイトル 公開日
第0回 このシリーズについて 2025/11/29
第1回 開発環境を整えよう 2025/11/29
第2回 GLFWでウィンドウを作る 2025/11/30
第3回 imguiを組み込む 2025/11/30
第4回 シェーダー入門 - 虹色の三角形を描く 2025/11/30
第5回 座標変換の基礎 2025/11/30
第6回 2D/3Dカメラの実装 2025/12/01
第7回 マウスでカメラ操作 2025/12/01
第8回 点・線・三角形を描く 2025/12/06
第9回 立方体と球体を描く - EBOで頂点を再利用 2026/01/03
第10回 バッチレンダリングで描画を高速化 2026/01/03

サンプルコードの入手方法

このシリーズのサンプルコードは、GitHubリポジトリで公開しています:

リポジトリ: https://github.com/an-embedded-engineer/PythonOpenGLBlog

各回に対応するタグ(例: v1.0, v2.0)が付けられているので、特定の回の状態を確認したい場合は、該当タグをチェックアウトしてください。

# リポジトリのクローン
git clone https://github.com/an-embedded-engineer/PythonOpenGLBlog.git

# 特定の回の状態を確認(例: 第1回)
git checkout v1.0

おわりに

次回から、実際の開発環境のセットアップに入ります。

GitHub Copilotと一緒に、Pythonで3Dプログラミングの世界を探索していきましょう!


次回: 第1回「開発環境を整えよう」

WSL + Visual Studio/Visual Studio CodeでC/C++開発環境構築

まえがき

WindowsでWSL(Windwos Subsystem for Linux)にLinux環境をインストールして、Visual Studio/Visual Studio CodeC/C++開発を行うための環境構築やTipsを記録として残しておきます。

今回はWSLのインストールおよび初期設定、起動・終了について記載します。


前提条件

今回の記事は以下の環境で実行しています。


WSL/Linuxディストリビューションインストール

1. デフォルトインストール

WSLとLinuxの規定のUbuntuディストリビューションをインストールする場合は、PowerShellで次のコマンドを実行することでインストールできます。

wsl --install


2. WSLバージョン更新

WSLのバージョンを更新するには以下のコマンドを実行します。

wsl --update

上記ではMicrosoft Storeから更新プログラムがダウンロードされます。
Microsoft Storeではなく、GitHubからダウンロードしたい場合は、--web-downloadオプションを付けてコマンドを実行します。

wsl --update --web-download


3. ディストリビューションの選択インストール

規定のUbuntuディストリビューション以外をインストールしたい場合は、次のコマンドで利用可能なディストリビューション一覧を表示することができます。

wsl --list --online


実行すると、以下のように結果が表示されます。

NAME                                   FRIENDLY NAME
Ubuntu                                 Ubuntu
Debian                                 Debian GNU/Linux
kali-linux                             Kali Linux Rolling
Ubuntu-18.04                           Ubuntu 18.04 LTS
Ubuntu-20.04                           Ubuntu 20.04 LTS
Ubuntu-22.04                           Ubuntu 22.04 LTS
Ubuntu-24.04                           Ubuntu 24.04 LTS
OracleLinux_7_9                        Oracle Linux 7.9
OracleLinux_8_7                        Oracle Linux 8.7
OracleLinux_9_1                        Oracle Linux 9.1
openSUSE-Leap-15.6                     openSUSE Leap 15.6
SUSE-Linux-Enterprise-15-SP5           SUSE Linux Enterprise 15 SP5
SUSE-Linux-Enterprise-Server-15-SP6    SUSE Linux Enterprise Server 15 SP6
openSUSE-Tumbleweed                    openSUSE Tumbleweed


「NAME」列の名前を指定することで該当のディストリビューションをインストールすることができます。

wsl --install Ubuntu-24.04


WSL/Linuxディストリビューション初期設定

1. デフォルトユーザ設定

wsl --installコマンドでディストリビューションをインストールすると、最初にデフォルトユーザ名とパスワードを聞かれますので、好きなユーザ名とパスワードを入力します。

インストール中: Ubuntu 24.04 LTS
Ubuntu 24.04 LTS がインストールされました。
Ubuntu 24.04 LTS を起動しています...
Installing, this may take a few minutes...
Please create a default UNIX user account. The username does not need to match your Windows username.
For more information visit: httpowershell://aka.ms/wslusers
Enter new UNIX username: <ユーザ名を入力>
New password:<パスワードを入力(タイプしても表示されない)>
Retype new password:<パスワードをもう一度入力(タイプしても表示されない)>
passwd: password updated successfully
Installation successful!
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

Welcome to Ubuntu 24.04 LTS (GNU/Linux 5.15.153.1-microsoft-standard-WSL2 x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

 System information as of Sun Aug  4 22:37:19 JST 2024

  System load:  0.04                Processes:             52
  Usage of /:   0.1% of 1006.85GB   Users logged in:       0
  Memory usage: 4%                  IPv4 address for eth0: 172.29.53.122
  Swap usage:   0%


This message is shown once a day. To disable it please create the
/home/<user_name>/.hushlogin file.


ユーザ名/パスワードを設定すると、インストールしたディストリビューションのシェルが立ち上がります。

<user_name>@<pc_name>:~$


2. rootパスワード設定

Ubuntuではインストール直後はrootのパスワードが設定されていないため、自分で設定する必要があります。
Ubuntu側のシェルで以下のコマンドを入力します。

~$ sudo passwd root


sudoコマンドを実行したアカウント(インストール時に設定したユーザ)のパスワードと、rootアカウントに設定するパスワードを入力します。

[sudo] password for <user_name>: <インストール時に設定したユーザのパスワード>
New password:<root用のパスワード>
Retype new password:<root用のパスワード再入力>
passwd: password updated successfully


su -コマンドでrootユーザに切り替えて、パスワードが設定されていることを確認します。

~$ su -
Password:<root用のパスワード>
root@<pc_name>:~#


exitコマンドで元のユーザに戻ります。

~# exit
logout
<user_name>@<pc_name>:~$


またはsu - <user_name>でも元のユーザに戻れます。

~# su - <user_name>
<user_name>@<pc_name>:~$


WSL起動/終了

1. ディストリビューションから抜ける(Linux シェル終了)

起動中のLinux シェルから抜けるためにはexitコマンドを実行します。 (先ほど)

~$ exit
logout
この操作を正しく終了しました。


2. WSL実行(Linuxシェル起動)

WSLを実行し、Linuxシェルを起動する場合はPowerShell上でwslコマンドを実行します。 引数に何も指定しない場合はデフォルトのUbuntuディストリビューションが起動します。

wsl


特定のディストリビューションを起動したい場合は-dオプションでディストリビューション名を指定します。

wsl -d Ubuntu-24.04


3. WSLシャットダウン

実行中のすべてのディストリビューションとWSL2の仮想マシンを終了するためには、PowerShellから以下のコマンドを実行します。
メモリの使用制限を変更する場合や .wslconfig ファイルを変更する場合など、WSL2仮想マシン環境の再起動を求められる場合に必要となります。

wsl --shutdown


4. ディストリビューション実行停止

指定したディストリビューションを終了/実行を停止するには、PowerShellから以下のコマンドを実行します。

wsl --terminate Ubuntu-24.04


その他の情報

Microsoft公式のWSLドキュメントを参照してください。


まとめ

今回はWSLのインストールおよび初期設定、起動・終了について説明しました。
次回は、Visual Studio/Visual Studio Codeでコーディング/デバッグを行うための初期設定について書こうと思います。

C++で任意のデータ型をシリアライズ - その4 使い方編

まえがき

C++で任意のデータをシリアライズするためのクラスの実装方法を紹介します。

今回は前回に引き続き任意のデータ型(クラス/構造体)に対してシリアライズできるようにする方法について説明します。

ソース一式はGitHubで公開しています。


前提条件

今回実装したソースは以下の環境でビルド、動作確認しています。

OS Ver Compiler Remarks
Windows 11 Visual Studio 2022(C++14)


実装

ユーザデータ型定義

まずは、ユーザが使用する任意のデータ型を定義します。
今回はサンプルとして、プリミティブ型、STLコンテナ型、列挙型、構造体型でそれぞれデータ型を定義しています。

/* 論理型 */
using BoolMessage = bool_t;

/* 符号付整数型(8bit) */
using Int8Message = int8_t;

/* 符号付整数型(16bit) */
using Int16Message = int16_t;

/* 符号付整数型(32bit) */
using Int32Message = int32_t;

/* 符号付整数型(64bit) */
using Int64Message = int64_t;

/* 符号無整数型(8bit) */
using UInt8Message = uint8_t;

/* 符号無整数型(16bit) */
using UInt16Message = uint16_t;

/* 符号無整数型(32bit) */
using UInt32Message = uint32_t;

/* 符号無整数型(64bit) */
using UInt64Message = uint64_t;

/* 浮動小数型(32bit) */
using Float32Message = float32_t;

/* 浮動小数型(64bit) */
using Float64Message = float64_t;

/* 文字列型 */
using StringMessage = string_t;

/* std::array型 */
using ArrayMessage = std::array<int32_t, 4>;

/* std::vector型 */
using VectorMessage = std::vector<float64_t>;

/* std::map型 */
using MapMessage = std::map<int32_t, float64_t>;

/* 列挙型(enum class) */
enum class EnumMessage1
{
    VALUE1,
    VALUE2,
    VALUE3,
    VALUE4,
};

/* 列挙型(enum) */
enum EnumMessage2
{
    ENUM_MSG2_VALUE1,
    ENUM_MSG2_VALUE2,
    ENUM_MSG2_VALUE3,
    ENUM_MSG2_VALUE4,
    ENUM_MSG2_VALUE5,
};

/* 構造体型1 */
struct StrucMessage1
{
    bool_t bool_value;
    uint32_t uint_value;
    float32_t float_value;
};

/* 構造体型2 */
struct StrucMessage2
{
    std::array<float32_t, 8> float_array;
    std::vector<uint64_t> ulong_vec;
};

/* 列挙型(メンバ用) */
enum class EnumType1
{
    ENUM_VALUE1,
    ENUM_VALUE2,
    ENUM_VALUE3,
};

/* 構造体型3 */
struct StrucMessage3
{
    string_t string_value;
    std::map<EnumType1, string_t> enum_str_map;
};

ユーザデータ型特性定義

リアライザクラスが参照するtype_traits::DataTypeTraits<T>GetMembersAsTuple()およびGetNamedMembersAsTuple()を定義します。

GetMembersAsTuple()では、シリアライズ対象となるすべてのメンバ変数をstd::tieで列挙して返すようにします。
GetNamedMembersAsTuple()では、シリアライズ対象となるメンバ変数とそのメンバ変数名をstd::tieで列挙し、さらにstd::make_tuple()でタプル型として返すようにします。
GetMembersAsTuple()GetNamedMembersAsTuple()のいずれも変更可能なもの(constなし)と、変更不可のもの(const付き)の2種類を定義します。

using namespace cpp_lib::type_traits;

/* app::StrucMessage1 */
template <>
struct DataTypeTraits<app::StrucMessage1>
{
    /* メンバをタプルで取得(変更不可) */
    static auto GetMembersAsTuple(const app::StrucMessage1& data)
    {
        return std::tie(data.bool_value, data.uint_value, data.float_value);
    }

    /* メンバをタプルで取得(変更可) */
    static auto GetMembersAsTuple(app::StrucMessage1& data)
    {
        return std::tie(data.bool_value, data.uint_value, data.float_value);
    }

    /* メンバを名前付きタプルで取得(変更不可) */
    static auto GetNamedMembersAsTuple(const app::StrucMessage1& data)
    {
        return std::make_tuple(std::tie("bool_value", data.bool_value), std::tie("uint_value", data.uint_value), std::tie("float_value", data.float_value));
    }

    /* メンバを名前付きタプルで取得(変更可) */
    static auto GetNamedMembersAsTuple(app::StrucMessage1& data)
    {
        return std::make_tuple(std::tie("bool_value", data.bool_value), std::tie("uint_value", data.uint_value), std::tie("float_value", data.float_value));
    }
};

/* app::StrucMessage2 */
template <>
struct DataTypeTraits<app::StrucMessage2>
{
    /* メンバをタプルで取得(変更不可) */
    static auto GetMembersAsTuple(const app::StrucMessage2& data)
    {
        return std::tie(data.float_array, data.ulong_vec);
    }

    /* メンバをタプルで取得(変更可) */
    static auto GetMembersAsTuple(app::StrucMessage2& data)
    {
        return std::tie(data.float_array, data.ulong_vec);
    }

    /* メンバを名前付きタプルで取得(変更不可) */
    static auto GetNamedMembersAsTuple(const app::StrucMessage2& data)
    {
        return std::make_tuple(std::tie("float_array", data.float_array), std::tie("ulong_vec", data.ulong_vec));
    }

    /* メンバを名前付きタプルで取得(変更可) */
    static auto GetNamedMembersAsTuple(app::StrucMessage2& data)
    {
        return std::make_tuple(std::tie("float_array", data.float_array), std::tie("ulong_vec", data.ulong_vec));
    }
};

/* app::StrucMessage3 */
template <>
struct DataTypeTraits<app::StrucMessage3>
{
    /* メンバをタプルで取得(変更不可) */
    static auto GetMembersAsTuple(const app::StrucMessage3& data)
    {
        return std::tie(data.string_value, data.enum_str_map);
    }

    /* メンバをタプルで取得(変更可) */
    static auto GetMembersAsTuple(app::StrucMessage3& data)
    {
        return std::tie(data.string_value, data.enum_str_map);
    }

    /* メンバを名前付きタプルで取得(変更不可) */
    static auto GetNamedMembersAsTuple(const app::StrucMessage3& data)
    {
        return std::make_tuple(std::tie("string_value", data.string_value), std::tie("enum_str_map", data.enum_str_map));
    }

    /* メンバを名前付きタプルで取得(変更可) */
    static auto GetNamedMembersAsTuple(app::StrucMessage3& data)
    {
        return std::make_tuple(std::tie("string_value", data.string_value), std::tie("enum_str_map", data.enum_str_map));
    }
};

ヘッダインクルード

ユーザデータ型特性を定義したヘッダ、シリアライザ(BinarySerialization、TextSerialization)のヘッダを同時にインクルードします。

これにより、独自で定義したデータ型に対するシリアライズ処理が実現できます。

/* 型定義&型特性定義 */
#include "UserDataTypes.h"
#include "UserDataTypeTraits.h"

/* シリアライザ */
#include "BinarySerialization.h"
#include "TextSerialization.h"
#include "Serializer.h"
#include "SerializerFactory.h"

明示的インスタンス

こちらは必須ではありませんが、テンプレートの明示的インスタンス化を行うことで、ユーザが定義した任意のデータ型に対するインスタンス化の回数を抑制し、プログラムの実行サイズやコンパイル時間を削減することができます。

前述のヘッダインクルードと合わせてUserTypeOperations.h/.cppなどのように1つのヘッダ・ソースにまとめて定義しておくと良いと思います。

明示的インスタンス化宣言(ヘッダ側)

ヘッダ側では明示的インスタンス化宣言を行います。
これにより、どこかで明示的インスタンス化が行われていることが宣言され、ヘッダインクルード側(利用側)でのインスタンス化が抑制されます(コンパイル時間の削減)。

namespace serialization
{
    /* バイナリ形式シリアライズ明示的インスタンス化宣言 */
    /* app::StrucMessage1 */
    extern template void BinarySerialization::Calculate<app::StrucMessage1>(const app::StrucMessage1& in_data, size_t& out_size, typename std::enable_if<std::is_class<app::StrucMessage1>::value>::type*);
    extern template void BinarySerialization::Serialize<app::StrucMessage1>(const app::StrucMessage1& in_data, size_t& offset, serialization::Archive& out_archive, typename std::enable_if<std::is_class<app::StrucMessage1>::value>::type*);
    extern template void BinarySerialization::Deserialize<app::StrucMessage1>(const serialization::Archive& in_archive, size_t& offset, app::StrucMessage1& out_data, typename std::enable_if<std::is_class<app::StrucMessage1>::value>::type*);

    /* app::StrucMessage2 */
    extern template void BinarySerialization::Calculate<app::StrucMessage2>(const app::StrucMessage2& in_data, size_t& out_size, typename std::enable_if<std::is_class<app::StrucMessage2>::value>::type*);
    extern template void BinarySerialization::Serialize<app::StrucMessage2>(const app::StrucMessage2& in_data, size_t& offset, serialization::Archive& out_archive, typename std::enable_if<std::is_class<app::StrucMessage2>::value>::type*);
    extern template void BinarySerialization::Deserialize<app::StrucMessage2>(const serialization::Archive& in_archive, size_t& offset, app::StrucMessage2& out_data, typename std::enable_if<std::is_class<app::StrucMessage2>::value>::type*);

    /* app::StrucMessage3 */
    extern template void BinarySerialization::Calculate<app::StrucMessage3>(const app::StrucMessage3& in_data, size_t& out_size, typename std::enable_if<std::is_class<app::StrucMessage3>::value>::type*);
    extern template void BinarySerialization::Serialize<app::StrucMessage3>(const app::StrucMessage3& in_data, size_t& offset, serialization::Archive& out_archive, typename std::enable_if<std::is_class<app::StrucMessage3>::value>::type*);
    extern template void BinarySerialization::Deserialize<app::StrucMessage3>(const serialization::Archive& in_archive, size_t& offset, app::StrucMessage3& out_data, typename std::enable_if<std::is_class<app::StrucMessage3>::value>::type*);


    /* テキスト形式シリアライズ明示的インスタンス化宣言 */
    /* app::StrucMessage1 */
    extern template void TextSerialization::Serialize<app::StrucMessage1>(const app::StrucMessage1& in_data, Node& out_node, typename std::enable_if<std::is_class<app::StrucMessage1>::value>::type*);
    extern template void TextSerialization::Deserialize<app::StrucMessage1>(const Node& in_node, app::StrucMessage1& out_data, typename std::enable_if<std::is_class<app::StrucMessage1>::value>::type*);

    /* app::StrucMessage2 */
    extern template void TextSerialization::Serialize<app::StrucMessage2>(const app::StrucMessage2& in_data, Node& out_node, typename std::enable_if<std::is_class<app::StrucMessage2>::value>::type*);
    extern template void TextSerialization::Deserialize<app::StrucMessage2>(const Node& in_node, app::StrucMessage2& out_data, typename std::enable_if<std::is_class<app::StrucMessage2>::value>::type*);

    /* app::StrucMessage3 */
    extern template void TextSerialization::Serialize<app::StrucMessage3>(const app::StrucMessage3& in_data, Node& out_node, typename std::enable_if<std::is_class<app::StrucMessage3>::value>::type*);
    extern template void TextSerialization::Deserialize<app::StrucMessage3>(const Node& in_node, app::StrucMessage3& out_data, typename std::enable_if<std::is_class<app::StrucMessage3>::value>::type*);
}

明示的インスタンス化定義(ソース側)

ソース側では明示的インスタンス化定義を行います。
これにより、ユーザが定義した任意のデータ型に対するインスタンス化があらかじめ行われ、シリアライザを利用する(呼び出す)側では、インスタンス化が抑制されます(実行ファイルのサイズ削減)。

namespace serialization
{
    /* バイナリ形式シリアライズ明示的インスタンス化 */
    /* app::StrucMessage1 */
    template void BinarySerialization::Calculate<app::StrucMessage1>(const app::StrucMessage1& in_data, size_t& out_size, typename std::enable_if<std::is_class<app::StrucMessage1>::value>::type*);
    template void BinarySerialization::Serialize<app::StrucMessage1>(const app::StrucMessage1& in_data, size_t& offset, serialization::Archive& out_archive, typename std::enable_if<std::is_class<app::StrucMessage1>::value>::type*);
    template void BinarySerialization::Deserialize<app::StrucMessage1>(const serialization::Archive& in_archive, size_t& offset, app::StrucMessage1& out_data, typename std::enable_if<std::is_class<app::StrucMessage1>::value>::type*);

    /* app::StrucMessage2 */
    template void BinarySerialization::Calculate<app::StrucMessage2>(const app::StrucMessage2& in_data, size_t& out_size, typename std::enable_if<std::is_class<app::StrucMessage2>::value>::type*);
    template void BinarySerialization::Serialize<app::StrucMessage2>(const app::StrucMessage2& in_data, size_t& offset, serialization::Archive& out_archive, typename std::enable_if<std::is_class<app::StrucMessage2>::value>::type*);
    template void BinarySerialization::Deserialize<app::StrucMessage2>(const serialization::Archive& in_archive, size_t& offset, app::StrucMessage2& out_data, typename std::enable_if<std::is_class<app::StrucMessage2>::value>::type*);

    /* app::StrucMessage3 */
    template void BinarySerialization::Calculate<app::StrucMessage3>(const app::StrucMessage3& in_data, size_t& out_size, typename std::enable_if<std::is_class<app::StrucMessage3>::value>::type*);
    template void BinarySerialization::Serialize<app::StrucMessage3>(const app::StrucMessage3& in_data, size_t& offset, serialization::Archive& out_archive, typename std::enable_if<std::is_class<app::StrucMessage3>::value>::type*);
    template void BinarySerialization::Deserialize<app::StrucMessage3>(const serialization::Archive& in_archive, size_t& offset, app::StrucMessage3& out_data, typename std::enable_if<std::is_class<app::StrucMessage3>::value>::type*);


    /* テキスト形式シリアライズ明示的インスタンス化 */
    /* app::StrucMessage1 */
    template void TextSerialization::Serialize<app::StrucMessage1>(const app::StrucMessage1& in_data, Node& out_node, typename std::enable_if<std::is_class<app::StrucMessage1>::value>::type*);
    template void TextSerialization::Deserialize<app::StrucMessage1>(const Node& in_node, app::StrucMessage1& out_data, typename std::enable_if<std::is_class<app::StrucMessage1>::value>::type*);

    /* app::StrucMessage2 */
    template void TextSerialization::Serialize<app::StrucMessage2>(const app::StrucMessage2& in_data, Node& out_node, typename std::enable_if<std::is_class<app::StrucMessage2>::value>::type*);
    template void TextSerialization::Deserialize<app::StrucMessage2>(const Node& in_node, app::StrucMessage2& out_data, typename std::enable_if<std::is_class<app::StrucMessage2>::value>::type*);

    /* app::StrucMessage3 */
    template void TextSerialization::Serialize<app::StrucMessage3>(const app::StrucMessage3& in_data, Node& out_node, typename std::enable_if<std::is_class<app::StrucMessage3>::value>::type*);
    template void TextSerialization::Deserialize<app::StrucMessage3>(const Node& in_node, app::StrucMessage3& out_data, typename std::enable_if<std::is_class<app::StrucMessage3>::value>::type*);
}

使用例

定義したユーザデータ型に対するシリアライズ/デシリアライズを行うサンプルコードを示します。

モード切替えコンパイルスイッチ

バイナリ形式のシリアライズとテキスト形式のシリアライズを切り替えるためのコンパイルスイッチマクロを定義しています。

/* シリアライズモード:バイナリ形式 */
#define SERIALIZE_MODE_BINARY   (0)
/* シリアライズモード:テキスト形式 */
#define SERIALIZE_MODE_TEXT     (1)

/* シリアライズモード切替え */
#define SERIALIZE_MODE          (SERIALIZE_MODE_BINARY)
シリアライズテスト関数

シリアライズのテストを行う共通関数を定義します。
正しくシリアライズ/デシリアライズ出来ているかを確認するために、入力データを乱数で生成し、入力データおよび、シリアライズ⇒デシリアライズ後の出力データの内容をダンプして比較できるようにしています。

乱数でのデータ生成およびデータのダンプ処理も、今回紹介したSwallowイディオムおよびIndex Tupleイディオムを用いて実装しています。
詳細はGitHubのコードを参照してください。
機会があれば別途紹介したいと思います。

/* シリアライズテスト */
template <typename T>
void SerializeTest(const std::string& type_name)
{
    /* ログダンプ用文字列ストリーム */
    std::stringstream ss;

    /* 入力データ */
    T in_data{};

    ss << "Serialize Test : " << type_name << std::endl;

    /* 入力データを乱数で生成 */
    cpp_lib::random::RandomDataGenerator::Generate(in_data);

    ss << "-----Before Dump----" << std::endl;
    /* 入力データをダンプ */
    ss << cpp_lib::dump::DataDumper::ToString(type_name, in_data);
    ss << "--------------------" << std::endl;

#if SERIALIZE_MODE == SERIALIZE_MODE_BINARY
    /* バイナリ形式用シリアライザインスタンス生成 */
    cpp_lib::serialization::Serializer<T>& serializer = cpp_lib::serialization::SerializerFactory<T>::CreateBinarySerializer();
#elif SERIALIZE_MODE == SERIALIZE_MODE_TEXT
    /* テキスト形式用シリアライザインスタンス生成 */
    cpp_lib::serialization::Serializer<T>& serializer = cpp_lib::serialization::SerializerFactory<T>::CreateTextSerializer();
#else
#error Invalid Serialize Mode : SERIALIZE_MODE
#endif

    /* アーカイブ */
    cpp_lib::serialization::Archive archive;

    /* シリアライズ */
    serializer.Serialize(in_data, archive);

    /* シリアライズ後のデータサイズ出力 */
    ss << std::endl;
    ss << "Serialized Size : " << archive.GetSize() << std::endl;

    /* テキスト形式の場合はテキストデータをダンプ */
#if SERIALIZE_MODE == SERIALIZE_MODE_TEXT
    ss << "------XML Dump------" << std::endl;
    ss << archive.GetDataPtr();
    ss << "--------------------" << std::endl;
#endif

    ss << std::endl;

    /* 出力データ */
    T out_data{};

    /* アーカイブをデシリアライズ */
    serializer.Deserialize(archive, out_data);

    ss << "-----After Dump----" << std::endl;
    /* 出力データをダンプ */
    ss << cpp_lib::dump::DataDumper::ToString(type_name, out_data);
    ss << "--------------------" << std::endl;;

    /* 文字列ストリームをコンソールに出力 */
    std::cout << ss.str() << std::endl;
    std::cout << std::endl;
    std::cout << std::endl;
}
メイン関数

メイン関数で定義した各ユーザデータ型に対するシリアライズテスト関数を呼び出します。

int main()
{
    /* 各ユーザ定義型に対するシリアライズテスト実行 */
    SerializeTest<app::BoolMessage>("BoolMessage");
    SerializeTest<app::Int8Message>("Int8Message");
    SerializeTest<app::Int16Message>("Int16Message");
    SerializeTest<app::Int32Message>("Int32Message");
    SerializeTest<app::Int64Message>("Int64Message");
    SerializeTest<app::UInt8Message>("UInt8Message");
    SerializeTest<app::UInt16Message>("UInt16Message");
    SerializeTest<app::UInt32Message>("UInt32Message");
    SerializeTest<app::UInt64Message>("UInt64Message");
    SerializeTest<app::Float32Message>("Float32Message");
    SerializeTest<app::Float64Message>("Float64Message");
    SerializeTest<app::StringMessage>("StringMessage");
    SerializeTest<app::ArrayMessage>("ArrayMessage");
    SerializeTest<app::VectorMessage>("VectorMessage");
    SerializeTest<app::MapMessage>("MapMessage");
    SerializeTest<app::EnumMessage1>("EnumMessage1");
    SerializeTest<app::EnumMessage2>("EnumMessage2");
    SerializeTest<app::StrucMessage1>("StrucMessage1");
    SerializeTest<app::StrucMessage2>("StrucMessage2");
    SerializeTest<app::StrucMessage3>("StrucMessage3");

    return 0;
}

実行結果

バイナリ形式、テキスト形式それぞれの実行結果を示します。
乱数でデータを生成しているため、実行するたびに異なる結果となります。

バイナリ形式

Serialize Test : BoolMessage
-----Before Dump----
- BoolMessage : true
--------------------

Serialized Size : 1

-----After Dump----
- BoolMessage : true
--------------------



Serialize Test : Int8Message
-----Before Dump----
- Int8Message : -97
--------------------

Serialized Size : 1

-----After Dump----
- Int8Message : -97
--------------------



Serialize Test : Int16Message
-----Before Dump----
- Int16Message : 557
--------------------

Serialized Size : 2

-----After Dump----
- Int16Message : 557
--------------------



Serialize Test : Int32Message
-----Before Dump----
- Int32Message : -470
--------------------

Serialized Size : 4

-----After Dump----
- Int32Message : -470
--------------------



Serialize Test : Int64Message
-----Before Dump----
- Int64Message : -113
--------------------

Serialized Size : 8

-----After Dump----
- Int64Message : -113
--------------------



Serialize Test : UInt8Message
-----Before Dump----
- UInt8Message : 85
--------------------

Serialized Size : 1

-----After Dump----
- UInt8Message : 85
--------------------



Serialize Test : UInt16Message
-----Before Dump----
- UInt16Message : 247
--------------------

Serialized Size : 2

-----After Dump----
- UInt16Message : 247
--------------------



Serialize Test : UInt32Message
-----Before Dump----
- UInt32Message : 1981
--------------------

Serialized Size : 4

-----After Dump----
- UInt32Message : 1981
--------------------



Serialize Test : UInt64Message
-----Before Dump----
- UInt64Message : 1620
--------------------

Serialized Size : 8

-----After Dump----
- UInt64Message : 1620
--------------------



Serialize Test : Float32Message
-----Before Dump----
- Float32Message : 0.276794
--------------------

Serialized Size : 4

-----After Dump----
- Float32Message : 0.276794
--------------------



Serialize Test : Float64Message
-----Before Dump----
- Float64Message : 921.443368
--------------------

Serialized Size : 8

-----After Dump----
- Float64Message : 921.443368
--------------------



Serialize Test : StringMessage
-----Before Dump----
- StringMessage : NsPtoRr6l
--------------------

Serialized Size : 18

-----After Dump----
- StringMessage : NsPtoRr6l
--------------------



Serialize Test : ArrayMessage
-----Before Dump----
- ArrayMessage
  -377, 887, -930, 571
--------------------

Serialized Size : 24

-----After Dump----
- ArrayMessage
  -377, 887, -930, 571
--------------------



Serialize Test : VectorMessage
-----Before Dump----
- VectorMessage
  886.725027, -888.930458, 653.026661, 600.011359, 17.384446, 923.696587, -418.051060, 560.708495, -860.235959
--------------------

Serialized Size : 80

-----After Dump----
- VectorMessage
  886.725027, -888.930458, 653.026661, 600.011359, 17.384446, 923.696587, -418.051060, 560.708495, -860.235959
--------------------



Serialize Test : MapMessage
-----Before Dump----
- MapMessage
  - [0]
    - first : -972
    - second : -76.153916
  - [1]
    - first : -786
    - second : -671.554764
  - [2]
    - first : -722
    - second : -101.852106
  - [3]
    - first : -698
    - second : -818.168871
  - [4]
    - first : -415
    - second : 912.755329
  - [5]
    - first : -242
    - second : -645.595839
  - [6]
    - first : 39
    - second : -959.489234
  - [7]
    - first : 369
    - second : -821.034684
  - [8]
    - first : 428
    - second : 79.652055
  - [9]
    - first : 597
    - second : 808.364866
  - [10]
    - first : 876
    - second : 794.861590
--------------------

Serialized Size : 140

-----After Dump----
- MapMessage
  - [0]
    - first : -972
    - second : -76.153916
  - [1]
    - first : -786
    - second : -671.554764
  - [2]
    - first : -722
    - second : -101.852106
  - [3]
    - first : -698
    - second : -818.168871
  - [4]
    - first : -415
    - second : 912.755329
  - [5]
    - first : -242
    - second : -645.595839
  - [6]
    - first : 39
    - second : -959.489234
  - [7]
    - first : 369
    - second : -821.034684
  - [8]
    - first : 428
    - second : 79.652055
  - [9]
    - first : 597
    - second : 808.364866
  - [10]
    - first : 876
    - second : 794.861590
--------------------



Serialize Test : EnumMessage1
-----Before Dump----
- EnumMessage1 : 1
--------------------

Serialized Size : 4

-----After Dump----
- EnumMessage1 : 1
--------------------



Serialize Test : EnumMessage2
-----Before Dump----
- EnumMessage2 : 2
--------------------

Serialized Size : 4

-----After Dump----
- EnumMessage2 : 2
--------------------



Serialize Test : StrucMessage1
-----Before Dump----
- StrucMessage1
  - bool_value : true
  - uint_value : 345
  - float_value : -412.287720
--------------------

Serialized Size : 9

-----After Dump----
- StrucMessage1
  - bool_value : true
  - uint_value : 345
  - float_value : -412.287720
--------------------



Serialize Test : StrucMessage2
-----Before Dump----
- StrucMessage2
  - float_array
    -945.414795, 771.739014, 445.108154, -849.261414, 124.613403, 327.225830, 341.188965, 99.228516
  - ulong_vec
    967, 415, 1863, 1580, 864, 1263, 1044, 173, 1742, 63, 1885, 247, 724, 1333, 378, 1391, 424, 1067, 617, 1894, 1574, 926, 1232, 1594, 1023, 746, 1857, 22, 1394, 1710
--------------------

Serialized Size : 288

-----After Dump----
- StrucMessage2
  - float_array
    -945.414795, 771.739014, 445.108154, -849.261414, 124.613403, 327.225830, 341.188965, 99.228516
  - ulong_vec
    967, 415, 1863, 1580, 864, 1263, 1044, 173, 1742, 63, 1885, 247, 724, 1333, 378, 1391, 424, 1067, 617, 1894, 1574, 926, 1232, 1594, 1023, 746, 1857, 22, 1394, 1710
--------------------



Serialize Test : StrucMessage3
-----Before Dump----
- StrucMessage3
  - string_value : neb5hpDH
  - enum_str_map
    - [0]
      - first : 1
      - second : tPhNWivV6ywsRz8
--------------------

Serialized Size : 53

-----After Dump----
- StrucMessage3
  - string_value : neb5hpDH
  - enum_str_map
    - [0]
      - first : 1
      - second : tPhNWivV6ywsRz8
--------------------

テキスト形式

Serialize Test : BoolMessage
-----Before Dump----
- BoolMessage : true
--------------------

Serialized Size : 24
------XML Dump------
{
    "root": "true"
}
--------------------

-----After Dump----
- BoolMessage : true
--------------------



Serialize Test : Int8Message
-----Before Dump----
- Int8Message : -66
--------------------

Serialized Size : 23
------XML Dump------
{
    "root": "-66"
}
--------------------

-----After Dump----
- Int8Message : -66
--------------------



Serialize Test : Int16Message
-----Before Dump----
- Int16Message : -660
--------------------

Serialized Size : 24
------XML Dump------
{
    "root": "-660"
}
--------------------

-----After Dump----
- Int16Message : -660
--------------------



Serialize Test : Int32Message
-----Before Dump----
- Int32Message : -450
--------------------

Serialized Size : 24
------XML Dump------
{
    "root": "-450"
}
--------------------

-----After Dump----
- Int32Message : -450
--------------------



Serialize Test : Int64Message
-----Before Dump----
- Int64Message : 968
--------------------

Serialized Size : 23
------XML Dump------
{
    "root": "968"
}
--------------------

-----After Dump----
- Int64Message : 968
--------------------



Serialize Test : UInt8Message
-----Before Dump----
- UInt8Message : 130
--------------------

Serialized Size : 23
------XML Dump------
{
    "root": "130"
}
--------------------

-----After Dump----
- UInt8Message : 130
--------------------



Serialize Test : UInt16Message
-----Before Dump----
- UInt16Message : 1021
--------------------

Serialized Size : 24
------XML Dump------
{
    "root": "1021"
}
--------------------

-----After Dump----
- UInt16Message : 1021
--------------------



Serialize Test : UInt32Message
-----Before Dump----
- UInt32Message : 451
--------------------

Serialized Size : 23
------XML Dump------
{
    "root": "451"
}
--------------------

-----After Dump----
- UInt32Message : 451
--------------------



Serialize Test : UInt64Message
-----Before Dump----
- UInt64Message : 569
--------------------

Serialized Size : 23
------XML Dump------
{
    "root": "569"
}
--------------------

-----After Dump----
- UInt64Message : 569
--------------------



Serialize Test : Float32Message
-----Before Dump----
- Float32Message : 131.467041
--------------------

Serialized Size : 30
------XML Dump------
{
    "root": "0x43037790"
}
--------------------

-----After Dump----
- Float32Message : 131.467041
--------------------



Serialize Test : Float64Message
-----Before Dump----
- Float64Message : 676.831881
--------------------

Serialized Size : 38
------XML Dump------
{
    "root": "0x408526a7b15eee50"
}
--------------------

-----After Dump----
- Float64Message : 676.831881
--------------------



Serialize Test : StringMessage
-----Before Dump----
- StringMessage : r4LhVs
--------------------

Serialized Size : 26
------XML Dump------
{
    "root": "r4LhVs"
}
--------------------

-----After Dump----
- StringMessage : r4LhVs
--------------------



Serialize Test : ArrayMessage
-----Before Dump----
- ArrayMessage
  711, 671, 775, -248
--------------------

Serialized Size : 117
------XML Dump------
{
    "root": {
        "item": "711",
        "item": "671",
        "item": "775",
        "item": "-248"
    }
}
--------------------

-----After Dump----
- ArrayMessage
  711, 671, 775, -248
--------------------



Serialize Test : VectorMessage
-----Before Dump----
- VectorMessage
  -918.270491, 567.724274, -829.875955, -347.300835
--------------------

Serialized Size : 176
------XML Dump------
{
    "root": {
        "item": "0xc08cb229f73fc5ba",
        "item": "0x4081bdcb5028a146",
        "item": "0xc089ef01f46f728d",
        "item": "0xc075b4d038311c5c"
    }
}
--------------------

-----After Dump----
- VectorMessage
  -918.270491, 567.724274, -829.875955, -347.300835
--------------------



Serialize Test : MapMessage
-----Before Dump----
- MapMessage
  - [0]
    - first : -572
    - second : 534.903451
  - [1]
    - first : -454
    - second : 717.369277
  - [2]
    - first : -344
    - second : -404.941074
  - [3]
    - first : -207
    - second : -824.382721
  - [4]
    - first : -170
    - second : 266.270653
  - [5]
    - first : 64
    - second : -5.214582
  - [6]
    - first : 178
    - second : 705.179553
  - [7]
    - first : 233
    - second : -208.877004
  - [8]
    - first : 545
    - second : 897.333639
  - [9]
    - first : 656
    - second : 875.125018
--------------------

Serialized Size : 1028
------XML Dump------
{
    "root": {
        "item": {
            "first": "-572",
            "second": "0x4080b73a445b0786"
        },
        "item": {
            "first": "-454",
            "second": "0x40866af4476c006c"
        },
        "item": {
            "first": "-344",
            "second": "0xc0794f0ea393ca94"
        },
        "item": {
            "first": "-207",
            "second": "0xc089c30fcff65380"
        },
        "item": {
            "first": "-170",
            "second": "0x4070a454981c5d8c"
        },
        "item": {
            "first": "64",
            "second": "0xc014dbbb4f84c080"
        },
        "item": {
            "first": "178",
            "second": "0x4086096fb9bb75ee"
        },
        "item": {
            "first": "233",
            "second": "0xc06a1c1069c4df70"
        },
        "item": {
            "first": "545",
            "second": "0x408c0aab4aab4776"
        },
        "item": {
            "first": "656",
            "second": "0x408b590009a903e0"
        }
    }
}
--------------------

-----After Dump----
- MapMessage
  - [0]
    - first : -572
    - second : 534.903451
  - [1]
    - first : -454
    - second : 717.369277
  - [2]
    - first : -344
    - second : -404.941074
  - [3]
    - first : -207
    - second : -824.382721
  - [4]
    - first : -170
    - second : 266.270653
  - [5]
    - first : 64
    - second : -5.214582
  - [6]
    - first : 178
    - second : 705.179553
  - [7]
    - first : 233
    - second : -208.877004
  - [8]
    - first : 545
    - second : 897.333639
  - [9]
    - first : 656
    - second : 875.125018
--------------------



Serialize Test : EnumMessage1
-----Before Dump----
- EnumMessage1 : 1
--------------------

Serialized Size : 21
------XML Dump------
{
    "root": "1"
}
--------------------

-----After Dump----
- EnumMessage1 : 1
--------------------



Serialize Test : EnumMessage2
-----Before Dump----
- EnumMessage2 : 3
--------------------

Serialized Size : 21
------XML Dump------
{
    "root": "3"
}
--------------------

-----After Dump----
- EnumMessage2 : 3
--------------------



Serialize Test : StrucMessage1
-----Before Dump----
- StrucMessage1
  - bool_value : true
  - uint_value : 1306
  - float_value : -944.752625
--------------------

Serialized Size : 121
------XML Dump------
{
    "root": {
        "bool_value": "true",
        "uint_value": "1306",
        "float_value": "0xc46c302b"
    }
}
--------------------

-----After Dump----
- StrucMessage1
  - bool_value : true
  - uint_value : 1306
  - float_value : -944.752625
--------------------



Serialize Test : StrucMessage2
-----Before Dump----
- StrucMessage2
  - float_array
    737.617188, 87.140381, -413.917358, 613.480347, 576.777954, 985.471558, -609.963989, -687.853149
  - ulong_vec
    1334, 1720, 1684, 935, 1735, 1085, 314, 977, 403, 1725, 392, 517, 1157
--------------------

Serialized Size : 722
------XML Dump------
{
    "root": {
        "float_array": {
            "item": "0x44386780",
            "item": "0x42ae47e0",
            "item": "0xc3cef56c",
            "item": "0x44195ebe",
            "item": "0x441031ca",
            "item": "0x44765e2e",
            "item": "0xc4187db2",
            "item": "0xc42bf69a"
        },
        "ulong_vec": {
            "item": "1334",
            "item": "1720",
            "item": "1684",
            "item": "935",
            "item": "1735",
            "item": "1085",
            "item": "314",
            "item": "977",
            "item": "403",
            "item": "1725",
            "item": "392",
            "item": "517",
            "item": "1157"
        }
    }
}
--------------------

-----After Dump----
- StrucMessage2
  - float_array
    737.617188, 87.140381, -413.917358, 613.480347, 576.777954, 985.471558, -609.963989, -687.853149
  - ulong_vec
    1334, 1720, 1684, 935, 1735, 1085, 314, 977, 403, 1725, 392, 517, 1157
--------------------



Serialize Test : StrucMessage3
-----Before Dump----
- StrucMessage3
  - string_value : 5Gx0tVhsKrhvw
  - enum_str_map
    - [0]
      - first : 1
      - second : oB
    - [1]
      - first : 2
      - second : kr7k2
--------------------

Serialized Size : 300
------XML Dump------
{
    "root": {
        "string_value": "5Gx0tVhsKrhvw",
        "enum_str_map": {
            "item": {
                "first": "1",
                "second": "oB"
            },
            "item": {
                "first": "2",
                "second": "kr7k2"
            }
        }
    }
}
--------------------

-----After Dump----
- StrucMessage3
  - string_value : 5Gx0tVhsKrhvw
  - enum_str_map
    - [0]
      - first : 1
      - second : oB
    - [1]
      - first : 2
      - second : kr7k2
--------------------

あとがき

今回は任意のデータ型(クラス/構造体)に対してシリアライズできるようにする方法について説明しました。
次回はおまけとして、SwallowイディオムおよびIndex Tupleイディオムを用いた、乱数でのデータ生成およびデータのダンプ処理について説明したいと思います。

C++で任意のデータ型をシリアライズ - その3 テキスト形式シリアライズ編

まえがき

C++で任意のデータをシリアライズするためのクラスの実装方法を紹介します。

今回は前回に引き続きテキスト形式でのシリアライズ処理の実装について説明します。

ソース一式はGitHubで公開しています。


前提条件

今回実装したソースは以下の環境でビルド、動作確認しています。

OS Ver Compiler Remarks
Windows 11 Visual Studio 2022(C++14)


実装

テキスト形式シリアライズ

テキスト形式シリアライズクラスでは、任意のデータ型をテキスト形式(XML or JSON)に変換して、変換されたテキスト(文字列)をバイト配列としてアーカイブに書き込む処理を行います。

ここでは、クラスの構成要素ごとに詳しく説明していきます。


ライブラリインクルード

テキスト形式へのシリアライズには、boostライブラリのproperty_tree機能を使用します。
そのため、下記のヘッダをインクルードします。
property_treeはヘッダオンリーのライブラリのため、ヘッダインクルードのみで使用可能です。

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/property_tree/json_parser.hpp>


マクロ

シリアライズの形式をXMLJSONで切り替えるためのマクロを定義します。

#define TEXT_SERIALIZE_MODE_XML     (0)
#define TEXT_SERIALIZE_MODE_JSON    (1)

#define TEXT_SERIALIZE_MODE         (TEXT_SERIALIZE_MODE_XML)


ノード

property_treeの木構造を表現するためのboost::property_tree::ptreeを、分かりやすいようにNodeという名前でエイリアス定義します。

/* ツリーノード型 */
using Node = boost::property_tree::ptree;


コンストラク

シリアライズクラスは複数インスタンスを生成する必要がないため、シングルトンとして実装しています。
メンバ変数などはないため、コンストラクタはデフォルト定義しています。

public:
    /* シングルトンインスタンス取得 */
    static TextSerialization& GetInstance()
    {
        static TextSerialization instance;
        return instance;
    }

private:
    /* コンストラクタ */
    TextSerialization() = default;

    /* デストラクタ */
    ~TextSerialization() = default;

public:
    /* コピー&ムーブセマンティクス無効化 */
    TextSerialization(const TextSerialization&) = delete;
    TextSerialization(TextSerialization&&) = delete;
    TextSerialization& operator=(const TextSerialization&) = delete;
    TextSerialization& operator=(TextSerialization&&) = delete;


インタフェース

インタフェース関数として、入力データをシリアライズしてアーカイブに変換するSerialize()アーカイブをデシリアライズして出力データに変換するDeserialize()を定義します。

/* 入力データをシリアライズしてアーカイブに変換 */
template <typename T>
void Serialize(const T& in_data, const string_t& name, Archive& out_archive)
{
    /* ルートノード */
    Node root_node{};

    /* シリアライズ */
    this->Serialize(in_data, name, root_node);

    /* シリアライズしたツリーノードをテキストに変換し、アーカイブにセット */
    this->WriteText(root_node, out_archive);
}

/* アーカイブをデシリアライズして出力データに変換 */
template <typename T>
void Deserialize(const Archive& in_archive, const string_t& name, T& out_data)
{
    /* ルートノード */
    Node root_node{};

    /* アーカイブをテキストに変換し、ツリーノードに変換 */
    this->ReadText(in_archive, root_node);

    /* デシリアライズ */
    this->Deserialize(root_node, name, out_data);
}

Serialize()ではNode型のルートノードを定義し、指定されたデータ型に応じたシリアライズ(ツリーノードに要素を追加)を行うSerialize()を呼び出します。構造体型などの場合は各メンバに対してSerialize()を呼び出し、すべてのデータをシリアライズします。
その後、シリアライズしたツリーノードをテキスト形式(文字列)に変換し、アーカイブにセットします。

Deserialize()では、アーカイブからテキストデータ(文字列)を取得し、指定形式(XML/JSON)でパース処理を行いツリーノードに変換します。 その後、指定されたデータ型に応じたDeserialize()を呼び出してデシリアライズ(ツリーノードから要素を取得し、元のデータに復元)します。構造体型などの場合は各メンバに対してDeserialize()を呼び出し、すべてのメンバをデシリアライズします。


内部関数

プライベートメンバ関数としては、データ型に応じたSerialize()Deserialize()やテキスト ⇔ アーカイブ変換処理を実装します。
データ型に応じた処理内容を詳しく説明していきます。


論理型・整数型

論理型・整数型では、そのままツリーノードに変換が可能なため、ツリーノードへの追加・取得を行う処理を実装します。

/* 整数型シリアライズ */
template<typename T, type_traits::concept_t<std::is_integral<T>::value> = nullptr>
void Serialize(const T& in_data, Node& out_node)
{
    /* ツリーノードに値を追加 */
    out_node.put_value(in_data);
}

/* 整数型デシリアライズ */
template<typename T, type_traits::concept_t<std::is_integral<T>::value> = nullptr>
void Deserialize(const Node& in_node, T& out_data)
{
    /* ツリーノードから要素取得 */
    if (auto value = in_node.get_value_optional<T>())
    {
        /* ツリーノード要素から値を取得 */
        out_data = value.get();
    }
    /* 要素取得失敗時は例外創出 */
    else
    {
        THROW_APP_EXCEPTION("Value is nothing");
    }
}

Serialize()ではput_value()でツリーノードにそのままデータを追加します。
Deserialize()では、get_value_optional<T>()でツリーノードから値の取得を試み、取得に成功したら値を取り出します。
値の取得に失敗した場合は異常な状態となっているため例外を送出します。


浮動小数

浮動小数型の場合は、そのままシリアライズしてテキストに変換すると、一定の桁数で丸められてしまい、デシリアライズで復元した際に、完全に同じ値にならないケースがあります。
そのため、浮動小数型を16進数の文字列に変換してシリアライズ(テキストに変換)し、デシリアライズ時には16進数文字列から元の浮動小数型に復元するようにします。

/* 浮動小数型シリアライズ */
template<typename T, type_traits::concept_t<std::is_floating_point<T>::value> = nullptr>
void Serialize(const T& in_data, Node& out_node)
{
    /* 16進数文字列 */
    string_t in_str{};

    /* 浮動小数データを16進数文字列に変換 */
    this->ToHexString(in_data, in_str);

    /* ツリーノードに16進数文字列に変換した値を追加 */
    out_node.put_value(in_str);
}

/* 浮動小数型デシリアライズ */
template<typename T, type_traits::concept_t<std::is_floating_point<T>::value> = nullptr>
void Deserialize(const Node& in_node, T& out_data)
{
    /* 16進数文字列 */
    string_t out_str{};

    /* ツリーノードから要素取得 */
    if (auto value = in_node.get_value_optional<string_t>())
    {
        /* ツリーノード要素から値を取得 */
        out_str = value.get();

        /* 取得した値(16進数文字列)を浮動小数データに変換 */
        this->FromHexString(out_str, out_data);
    }
    /* 要素取得失敗時は例外創出 */
    else
    {
        THROW_APP_EXCEPTION("Value is nothing");
    }
}

Serialize()ではToHexString()で16進数文字列に変換し、put_value()でツリーノードにデータを追加します。
Deserialize()では、get_value_optional<T>()でツリーノードから文字列(string)として値の取得を試み、取得に成功したら値を取り出し、FromHexString()で元の浮動小数型に復元しています。
値の取得に失敗した場合は異常な状態となっているため例外を送出します。

/* 浮動小数データ(32bit)を16進数文字列に変換 */
inline void ToHexString(const float32_t& in_data, string_t& out_data)
{
    /* 文字列ストリーム */
    std::stringstream ss;

    /* 浮動小数 <=> 整数変換器 */
    binary::FloatIntConverter<float32_t, uint32_t> converter;

    /* 浮動小数 => 整数変換 */
    uint32_t int_data = converter.ConvertToInt(in_data);

    /* 変換した整数を16進数文字列として文字列ストリームに追加 */
    ss << std::showbase;
    ss << std::setw(4);
    ss << std::hex << int_data;

    /* 文字列に変換 */
    out_data = ss.str();
}

/* 浮動小数データ(64bit)を16進数文字列に変換 */
inline void ToHexString(const float64_t& in_data, string_t& out_data)
{
    /* 文字列ストリーム */
    std::stringstream ss;

    /* 浮動小数 <=> 整数変換器 */
    binary::FloatIntConverter<float64_t, uint64_t> converter;

    /* 浮動小数 => 整数変換 */
    uint64_t int_data = converter.ConvertToInt(in_data);

    /* 変換した整数を16進数文字列として文字列ストリームに追加 */
    ss << std::showbase;
    ss << std::setw(8);
    ss << std::hex << int_data;

    /* 文字列に変換 */
    out_data = ss.str();
}

/* 16進数文字列を浮動小数(32bit)データに変換 */
inline void FromHexString(const string_t& in_data, float32_t& out_data)
{
    /* 16進数文字列を整数に変換 */
    uint32_t int_data = std::stoul(in_data, nullptr, 16);

    /* 浮動小数 <=> 整数変換器 */
    binary::FloatIntConverter<float32_t, uint32_t> converter;

    /* 整数 => 浮動小数変換 */
    out_data = converter.ConvertToFloat(int_data);
}

/* 16進数文字列を浮動小数(64bit)データに変換 */
inline void FromHexString(const string_t& in_data, float64_t& out_data)
{
    /* 16進数文字列を整数に変換 */
    uint64_t int_data = std::stoull(in_data, nullptr, 16);

    /* 浮動小数 <=> 整数変換器 */
    binary::FloatIntConverter<float64_t, uint64_t> converter;

    /* 整数 => 浮動小数変換 */
    out_data = converter.ConvertToFloat(int_data);
}

ToHexString()では、binary::FloatIntConverterConvertToInt()で一旦元の浮動小数型と同じビットサイズの符号無整数型に変換し、std::stringstreamで16進数の文字列に変換しています。
FromHexString()では、16進数文字列をstd::stoul()std::stoull()で元の浮動小数型と同じビットサイズの符号無整数型に変換し、binary::FloatIntConverterConvertToFloat()で元の浮動小数型に復元しています。
binary::FloatIntConverterの詳細については、GitHubのソースをご参照ください。


列挙型

列挙型(特にenum class)では、ツリーノードに変換できないため、基底型に変換して処理を行うようにしています。

/* 列挙型(enum/enum class)シリアライズ */
template<typename T, type_traits::concept_t<std::is_enum<T>::value> = nullptr>
void Serialize(const T& in_data, Node& out_node)
{
    /* 入力データの基底型にキャストしてシリアライズ */
    this->Serialize(type_traits::underlying_cast(in_data), out_node);
}

/* 列挙型(enum/enum class)デシリアライズ */
template<typename T, type_traits::concept_t<std::is_enum<T>::value> = nullptr>
void Deserialize(const Node& in_node, T& out_data)
{
    /* 入力データの基底型データ */
    type_traits::underlying_type_t<T> data{};

    /* 入力データの基底型としてデシリアライズ */
    this->Deserialize(in_node, data);

    /* 入力データの型にキャストしてセット */
    out_data = static_cast<T>(data);
}

Serialize()では、type_traits::underlying_castを用いて基底型(デフォルトではint)に変換してSerialize()を呼び出しています。
Deserialize()では、type_traits::underlying_type_t<T>で基底型の一時変数を用意し、基底型に対するDeserialize()を呼び出してから元のデータ型にstatic_castで変換しています。
type_traits::underlying_castおよびtype_traits::underlying_type_t<T>の詳細についてはGitHubのソースをご参照ください。


STLコンテナ型

STLコンテナ(std::arraystd::vectorstd::pairstd::mapなど)に対しては、子ノードを追加し、その子ノードに対して、さらにコンテナに格納されている各要素に対するシリアライズ/デシリアライズ処理を行います。

/* std::array型シリアライズ */
template <typename T, size_t N>
void Serialize(const std::array<T, N>& in_data, const string_t& name, Node& out_node)
{
    /* 名前を指定して子ノードを追加 */
    auto& child_node = out_node.add(name, "");

    /* 全要素を走査 */
    for (const auto& item : in_data)
    {
        /* 各要素をシリアライズして子ノードに追加 */
        this->Serialize(item, "item", child_node);
    }
}

/* std::array型デシリアライズ */
template <typename T, size_t N>
void Deserialize(const Node& in_node, const string_t& name, std::array<T, N>& out_data)
{
    /* 名前を指定して子ノードを取得 */
    auto& child_node = in_node.get_child(name);

    /* インデックス */
    size_t i = 0;

    /* 子ノードの全要素を走査 */
    for (const auto& item_node_pair : child_node)
    {
        /* 要素 */
        T item{};

        /* 各要素をデシリアライズ */
        this->Deserialize(item_node_pair.second, item);

        /* 要素をセット */
        out_data[i] = item;

        /* インデックスインクリメント */
        i++;
    }
}


/* std::vector型シリアライズ */
template <typename T>
void Serialize(const std::vector<T>& in_data, const string_t& name, Node& out_node)
{
    /* 名前を指定して子ノードを追加 */
    auto& child_node = out_node.add(name, "");

    /* 全要素を走査 */
    for (const auto& item : in_data)
    {
        /* 各要素をシリアライズして子ノードに追加 */
        this->Serialize(item, "item", child_node);
    }
}

/* std::vector型デシリアライズ */
template <typename T>
void Deserialize(const Node& in_node, const string_t& name, std::vector<T>& out_data)
{
    /* 名前を指定して子ノードを取得 */
    auto& child_node = in_node.get_child(name);

    /* 出力データクリア */
    out_data.clear();

    /* 子ノードの要素数分メモリを事前確保 */
    out_data.reserve(child_node.size());

    /* 子ノードの全要素を走査 */
    for (const auto& item_node_pair : child_node)
    {
        /* 要素 */
        T item{};

        /* 各要素をデシリアライズ */
        this->Deserialize(item_node_pair.second, item);

        /* 要素を追加 */
        out_data.push_back(item);
    }
}


/* std::pair型シリアライズ */
template <typename T1, typename T2>
void Serialize(const std::pair<T1, T2>& in_data, Node& out_node)
{
    /* 1st要素シリアライズ */
    this->Serialize(in_data.first, "first", out_node);

    /* 2nd要素シリアライズ */
    this->Serialize(in_data.second, "second", out_node);
}

/* std::pair型デシリアライズ */
template <typename T1, typename T2>
void Deserialize(const Node& in_node, std::pair<T1, T2>& out_data)
{
    /* 1st要素 */
    T1 first{};

    /* 1st要素デシリアライズ */
    this->Deserialize(in_node, "first", first);

    /* 2nd要素 */
    T2 second{};

    /* 2nd要素デシリアライズ */
    this->Deserialize(in_node, "second", second);

    /* std::pairに変換してセット */
    out_data = std::make_pair<T1, T2>(first, second);
}


/* std::map型シリアライズ */
template <typename TKey, typename TValue>
void Serialize(const std::map<TKey, TValue>& in_data, const string_t& name, Node& out_node)
{
    /* 名前を指定して子ノードを追加 */
    auto& child_node = out_node.add(name, "");

    /* 全要素を走査 */
    for (const auto& item : in_data)
    {
        /* 各要素をシリアライズして子ノードに追加 */
        this->Serialize(item, "item", child_node);
    }
}

/* std::map型デシリアライズ */
template <typename TKey, typename TValue>
void Deserialize(const Node& in_node, const string_t& name, std::map<TKey, TValue>& out_data)
{
    /* 名前を指定して子ノードを取得 */
    auto& child_node = in_node.get_child(name);

    /* 子ノードの全要素を走査 */
    for (const auto& item_node_pair : child_node)
    {
        /* キー要素 */
        TKey key{};

        /* キー要素デシリアライズ */
        this->Deserialize(item_node_pair.second, "first", key);

        /* 値要素 */
        TValue value{};

        /* 値要素デシリアライズ */
        this->Deserialize(item_node_pair.second, "second", value);

        /* キー/値を追加 */
        out_data.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(value));
    }
}

例としてstd::arrayに対するシリアライズ/デシリアライズ処理について説明します。

Serialize()では、指定された名前(メンバ変数名)で子ノード(chile_node)を追加し、コンテナの各要素に対してシリアライズを行うSerialize()を呼び出し、子ノード(chile_node)の子供(item)としてノードを追加していきます。

Deserialize()では、指定された名前(メンバ変数名)で子ノード(chile_node)を取得し、その子ノード(chile_node)の子供(item)の要素数Deserialize()を呼び出して各要素をデシリアライズします。

std::vectorstd::mapも同様に実装しています。

std::pairはメンバのfirstsecondに対してそれぞれSerialize()Deserialize()を行うようにしています。


文字列型

文字列型では、整数型と同様、そのままツリーノードに変換が可能なため、ツリーノードへの追加・取得を行う処理を実装します。

/* 文字列型シリアライズ */
inline void Serialize(const string_t& in_data, Node& out_node)
{
    /* ツリーノードに値を追加 */
    out_node.put_value(in_data);
}

/* 文字列型デシリアライズ */
inline void Deserialize(const Node& in_node, string_t& out_data)
{
    /* ツリーノードから要素取得 */
    if (auto value = in_node.get_value_optional<string_t>())
    {
        /* ツリーノード要素から値を取得 */
        out_data = value.get();
    }
    /* 要素取得失敗時は例外創出 */
    else
    {
        THROW_APP_EXCEPTION("Value is nothing");
    }
}


クラス/構造体型

クラス/構造体型では、バイナリ形式の場合と同様、メンバ変数をタプル(std::tuple)型で列挙して各メンバに対してSerialize()Deserialize()を呼び出しています。

/* クラス/構造体(class/struct)型メンバシリアライズ */
template <typename TUPLE, size_t ...I>
void SerializeTupleImple(Node& out_node, TUPLE&& t, std::index_sequence<I...>)
{
    /* swallowイディオムでクラス/構造体(class/struct)型メンバのタプルを順次展開し、シリアライズ */
    using swallow = std::initializer_list<int>;

    (void)swallow
    {
        (void(this->Serialize(std::get<1>(std::get<I>(t)), std::get<0>(std::get<I>(t)), out_node)), 0)...
    };
}

/* クラス/構造体(class/struct)型メンバデシリアライズ */
template <typename TUPLE, size_t ...I>
void DeserializeTupleImple(const Node& in_node, TUPLE&& t, std::index_sequence<I...>)
{
    /* swallowイディオムでクラス/構造体(class/struct)型メンバのタプルを順次展開し、デシリアライズ */
    using swallow = std::initializer_list<int>;

    (void)swallow
    {
        (void(this->Deserialize(in_node, std::get<0>(std::get<I>(t)), std::get<1>(std::get<I>(t)))), 0)...
    };
}

/* クラス/構造体(class/struct)型メンバタプルシリアライズ */
template <typename TUPLE>
void SerializeTuple(Node& out_node, TUPLE&& t)
{
    /* クラス/構造体(class/struct)型メンバシリアライズ */
    this->SerializeTupleImple(out_node, std::forward<TUPLE>(t), std::make_index_sequence<std::tuple_size<std::decay_t<TUPLE>>::value>{});
}

/* クラス/構造体(class/struct)型メンバタプルデシリアライズ */
template <typename TUPLE>
void DeserializeTuple(const Node& in_node, TUPLE&& t)
{
    /* クラス/構造体(class/struct)型メンバデシリアライズ */
    this->DeserializeTupleImple(in_node, std::forward<TUPLE>(t), std::make_index_sequence<std::tuple_size<std::decay_t<TUPLE>>::value>{});
}


/* クラス/構造体(class/struct)型シリアライズ */
template<class T>
void Serialize(const T& in_data, Node& out_node, typename std::enable_if<std::is_class<T>::value>::type* = nullptr)
{
    /* メンバの名前付きタプル取得 */
    auto tuple = type_traits::DataTypeTraits<T>::GetNamedMembersAsTuple(in_data);

    /* メンバの名前付きタプルから各メンバをシリアライズ */
    this->SerializeTuple(out_node, tuple);
}

/* クラス/構造体(class/struct)型デシリアライズ */
template<class T>
void Deserialize(const Node& in_node, T& out_data, typename std::enable_if<std::is_class<T>::value>::type* = nullptr)
{
    /* メンバの名前付きタプル取得 */
    auto tuple = type_traits::DataTypeTraits<T>::GetNamedMembersAsTuple(out_data);

    /* メンバの名前付きタプルから各メンバをデシリアライズ */
    this->DeserializeTuple(in_node, tuple);
}

Serialize()を例に説明します。
Serialize()では、type_traits::DataTypeTraits<T>::GetNamedMembersAsTuple()を呼び出すことで、入力データのメンバとそのメンバ名をタプル(std::tuple)型で取得し、SerializeTuple()に渡しています。
SerializeTuple()ではstd::forwardでタプルデータを渡し、またstd::make_index_sequence<std::tuple_size<std::decay_t<TUPLE>>::value>{}でタプルの要素数分のインデックスシーケンスを生成して渡しています。
さらにSerializeTupleImple()では、渡されたタプルと要素のインデックスにより指定インデックス位置のメンバ変数を取り出し、メンバの型に対応したSerialize()を呼び出しています。
タプル要素インデックス(I)は可変長テンプレートパラメータとなっており、SwallowイディオムおよびIndex Tupleイディオムによって順次展開されるため、すべてのタプルに含まれるすべてのメンバに対して順番にSerialize()が呼ばれる仕組みになっています。

type_traits::DataTypeTraits<T>::GetNamedMembersAsTuple()の定義方法は後ほど使い方の項で説明します。


名前付きオブジェクト

メンバ変数やコンテナ要素など同じ階層に複数のデータが含まれている場合には、名前を付けてどのデータかを特定できるようにする必要があります。
そういった名前付きオブジェクトのシリアライズ/デシリアライズを共通化しています。

/* 名前付きオブジェクトシリアライズ */
template <typename T>
void Serialize(const T& in_data, const string_t& name, Node& out_node)
{
    /* ツリーノードに名前を指定して子ノード追加 */
    auto& child_node = out_node.add(name, "");

    /* 入力データをシリアライズして子ノードに追加 */
    this->Serialize(in_data, child_node);
}

/* 名前付きオブジェクトデシリアライズ */
template <typename T>
void Deserialize(const Node& in_node, const string_t& name, T& out_data)
{
    /* ツリーノードに名前を指定して子ノード取得 */
    auto& child_node = in_node.get_child(name);

    /* 子ノードからデータをデシリアライズ */
    this->Deserialize(child_node, out_data);
}

Serialize()では、指定された名前(メンバ変数名など)で子ノード(chile_node)を追加し、子ノードに対してSerialize()を呼び出し入力データをノードに追加します。

Deserialize()では、指定された名前(メンバ変数名など)で子ノード(chile_node)を取得し、子ノードを指定してDeserialize()を呼び出して、データを復元します。


ツリーノード ⇔ アーカイブ変換

ツリーノードとアーカイブを相互変換する関数を用意しています。
この時に使用するboostライブラリの関数を使い分けることで、XML形式とJSON形式を切り替えることができます。

/* シリアライズしたツリーノードをテキストに変換し、アーカイブにセット */
inline void WriteText(const Node& in_root_node, Archive& out_archive)
{
    /* 文字列ストリーム */
    std::stringstream ss;

#if TEXT_SERIALIZE_MODE == TEXT_SERIALIZE_MODE_XML
    /* インデントサイズ */
    const int indent = 2;

    /* XML書き込み設定(インデント文字、インデントサイズ、エンコーディング) */
    auto setting = boost::property_tree::xml_parser::xml_writer_make_settings<string_t>(' ', indent, boost::property_tree::xml_parser::widen<string_t>("utf-8"));

    /* XMLとして文字列ストリームに書き込み */
    boost::property_tree::write_xml(ss, in_root_node, setting);
#elif TEXT_SERIALIZE_MODE == TEXT_SERIALIZE_MODE_JSON
    /* JSONとして文字列ストリームに書き込み */
    boost::property_tree::write_json(ss, in_root_node);
#else
#error Invalid Serialize Mode TEXT_SERIALIZE_MODE
#endif

    /* 文字列に変換 */
    string_t in_str = ss.str();

    /* 文字列長を取得 */
    size_t text_len = in_str.length();

    /* バッファサイズ算出(文字列長 + 終端文字(null文字)サイズ) */
    size_t buffer_size = text_len + 1;

    /* シリアライズ後のデータサイズでアーカイブのデータバッファメモリ確保 */
    out_archive.Reserve(buffer_size);

    /* オフセット初期化 */
    size_t offset = 0;

    /* 指定オフセット位置から文字列データ書き込み */
    out_archive.Write(in_str, offset);
}

/* アーカイブをテキストに変換し、ツリーノードに変換 */
inline void ReadText(const Archive& in_archive, Node& out_root_node)
{
    /* 文字列ストリーム */
    std::stringstream ss;

    /* アーカイブのデータバッファ(ポインタ)を文字列ストリームに追加 */
    ss << in_archive.GetDataPtr();

#if TEXT_SERIALIZE_MODE == TEXT_SERIALIZE_MODE_XML
    /* 文字列ストリームをXMLとしてツリーノードに読み込み */
    boost::property_tree::read_xml(ss, out_root_node);
#elif TEXT_SERIALIZE_MODE == TEXT_SERIALIZE_MODE_JSON
    /* 文字列ストリームをJSONとしてツリーノードに読み込み */
    boost::property_tree::read_json(ss, out_root_node);
#else
#error Invalid Serialize Mode TEXT_SERIALIZE_MODE
#endif
}

WriteText()では、シリアライズしたツリーノードをテキストに変換し、アーカイブにセットします。
boost::property_tree::write_xml()またはboost::property_tree::write_json()を呼び出すことでツリーノードからテキスト(XML/JSON)に変換し、文字列ストリームに追加することができます。
XMLの場合には、boost::property_tree::xml_parser::xml_writer_make_settings<string_t>()で、XMLに変換する際の設定(インデントなど)を変更できます。
その後、文字列ストリームに追加したテキストデータを文字列に変換し、文字列長 + 終端にnull文字('\0')の長さ分アーカイブのメモリを確保し、文字列データをバイト配列としてアーカイブにコピーしています。

ReadText()では、アーカイブをテキストに変換し、ツリーノードに変換します。
アーカイブのバッファデータのポインタをそのまま文字列ストリームに追加することでテキストデータを取り出すことができます。
その後、boost::property_tree::read_xml()またはboost::property_tree::read_json()を呼び出すことでテキストデータ(XML/JSON)をパースし、ツリーノードに変換することができます。


ソース全体は以下の通りです。

テキスト形式シリアライズクラス

#pragma once
#include "CommonTypes.h"
#include "TypeTraits.h"
#include "DataTypeTraits.h"
#include "Archive.h"
#include "FloatIntConverter.h"
#include "AppException.h"
#include "StringFormat.h"

#include <sstream>
#include <iomanip>

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/property_tree/json_parser.hpp>

#define TEXT_SERIALIZE_MODE_XML     (0)
#define TEXT_SERIALIZE_MODE_JSON    (1)

#define TEXT_SERIALIZE_MODE         (TEXT_SERIALIZE_MODE_JSON)

namespace cpp_lib
{
    namespace serialization
    {
        /* テキスト形式シリアライズクラス */
        class TextSerialization
        {
        public:
            /* ツリーノード型 */
            using Node = boost::property_tree::ptree;

        public:
            /* シングルトンインスタンス取得 */
            static TextSerialization& GetInstance()
            {
                static TextSerialization instance;
                return instance;
            }

        private:
            /* コンストラクタ */
            TextSerialization() = default;

            /* デストラクタ */
            ~TextSerialization() = default;

        public:
            /* コピー&ムーブセマンティクス無効化 */
            TextSerialization(const TextSerialization&) = delete;
            TextSerialization(TextSerialization&&) = delete;
            TextSerialization& operator=(const TextSerialization&) = delete;
            TextSerialization& operator=(TextSerialization&&) = delete;

        public:
            /* 入力データをシリアライズしてアーカイブに変換 */
            template <typename T>
            void Serialize(const T& in_data, const string_t& name, serialization::Archive& out_archive)
            {
                /* ルートノード */
                Node root_node{};

                /* シリアライズ */
                this->Serialize(in_data, name, root_node);

                /* シリアライズしたツリーノードをテキストに変換し、アーカイブにセット */
                this->WriteText(root_node, out_archive);
            }

            /* アーカイブをデシリアライズして出力データに変換 */
            template <typename T>
            void Deserialize(const serialization::Archive& in_archive, const string_t& name, T& out_data)
            {
                /* ルートノード */
                Node root_node{};

                /* アーカイブをテキストに変換し、ツリーノードに変換 */
                this->ReadText(in_archive, root_node);

                /* デシリアライズ */
                this->Deserialize(root_node, name, out_data);
            }

        private:
            /* 整数型シリアライズ */
            template<typename T, cpp_lib::type_traits::concept_t<std::is_integral<T>::value> = nullptr>
            void Serialize(const T& in_data, Node& out_node)
            {
                /* ツリーノードに値を追加 */
                out_node.put_value(in_data);
            }

            /* 整数型デシリアライズ */
            template<typename T, cpp_lib::type_traits::concept_t<std::is_integral<T>::value> = nullptr>
            void Deserialize(const Node& in_node, T& out_data)
            {
                /* ツリーノードから要素取得 */
                if (auto value = in_node.get_value_optional<T>())
                {
                    /* ツリーノード要素から値を取得 */
                    out_data = value.get();
                }
                /* 要素取得失敗時は例外創出 */
                else
                {
                    THROW_APP_EXCEPTION("Value is nothing");
                }
            }


            /* 浮動小数型シリアライズ */
            template<typename T, cpp_lib::type_traits::concept_t<std::is_floating_point<T>::value> = nullptr>
            void Serialize(const T& in_data, Node& out_node)
            {
                /* 16進数文字列 */
                string_t in_str{};

                /* 浮動小数データを16進数文字列に変換 */
                this->ToHexString(in_data, in_str);

                /* ツリーノードに16進数文字列に変換した値を追加 */
                out_node.put_value(in_str);
            }

            /* 浮動小数型デシリアライズ */
            template<typename T, cpp_lib::type_traits::concept_t<std::is_floating_point<T>::value> = nullptr>
            void Deserialize(const Node& in_node, T& out_data)
            {
                /* 16進数文字列 */
                string_t out_str{};

                /* ツリーノードから要素取得 */
                if (auto value = in_node.get_value_optional<string_t>())
                {
                    /* ツリーノード要素から値を取得 */
                    out_str = value.get();

                    /* 取得した値(16進数文字列)を浮動小数データに変換 */
                    this->FromHexString(out_str, out_data);
                }
                /* 要素取得失敗時は例外創出 */
                else
                {
                    THROW_APP_EXCEPTION("Value is nothing");
                }
            }

            /* 列挙型(enum/enum class)シリアライズ */
            template<typename T, cpp_lib::type_traits::concept_t<std::is_enum<T>::value> = nullptr>
            void Serialize(const T& in_data, Node& out_node)
            {
                /* 入力データの基底型にキャストしてシリアライズ */
                this->Serialize(cpp_lib::type_traits::underlying_cast(in_data), out_node);
            }

            /* 列挙型(enum/enum class)デシリアライズ */
            template<typename T, cpp_lib::type_traits::concept_t<std::is_enum<T>::value> = nullptr>
            void Deserialize(const Node& in_node, T& out_data)
            {
                /* 入力データの基底型データ */
                cpp_lib::type_traits::underlying_type_t<T> data{};

                /* 入力データの基底型としてデシリアライズ */
                this->Deserialize(in_node, data);

                /* 入力データの型にキャストしてセット */
                out_data = static_cast<T>(data);
            }

            /* std::array型シリアライズ */
            template <typename T, size_t N>
            void Serialize(const std::array<T, N>& in_data, const string_t& name, Node& out_node)
            {
                /* 名前を指定して子ノードを追加 */
                auto& child_node = out_node.add(name, "");

                /* 全要素を走査 */
                for (const auto& item : in_data)
                {
                    /* 各要素をシリアライズして子ノードに追加 */
                    this->Serialize(item, "item", child_node);
                }
            }

            /* std::array型デシリアライズ */
            template <typename T, size_t N>
            void Deserialize(const Node& in_node, const string_t& name, std::array<T, N>& out_data)
            {
                /* 名前を指定して子ノードを取得 */
                auto& child_node = in_node.get_child(name);

                /* インデックス */
                size_t i = 0;

                /* 子ノードの全要素を走査 */
                for (const auto& item_node_pair : child_node)
                {
                    /* 要素 */
                    T item{};

                    /* 各要素をデシリアライズ */
                    this->Deserialize(item_node_pair.second, item);

                    /* 要素をセット */
                    out_data[i] = item;

                    /* インデックスインクリメント */
                    i++;
                }
            }


            /* std::vector型シリアライズ */
            template <typename T>
            void Serialize(const std::vector<T>& in_data, const string_t& name, Node& out_node)
            {
                /* 名前を指定して子ノードを追加 */
                auto& child_node = out_node.add(name, "");

                /* 全要素を走査 */
                for (const auto& item : in_data)
                {
                    /* 各要素をシリアライズして子ノードに追加 */
                    this->Serialize(item, "item", child_node);
                }
            }

            /* std::vector型デシリアライズ */
            template <typename T>
            void Deserialize(const Node& in_node, const string_t& name, std::vector<T>& out_data)
            {
                /* 名前を指定して子ノードを取得 */
                auto& child_node = in_node.get_child(name);

                /* 出力データクリア */
                out_data.clear();

                /* 子ノードの要素数分メモリを事前確保 */
                out_data.reserve(child_node.size());

                /* 子ノードの全要素を走査 */
                for (const auto& item_node_pair : child_node)
                {
                    /* 要素 */
                    T item{};

                    /* 各要素をデシリアライズ */
                    this->Deserialize(item_node_pair.second, item);

                    /* 要素を追加 */
                    out_data.push_back(item);
                }
            }


            /* std::pair型シリアライズ */
            template <typename T1, typename T2>
            void Serialize(const std::pair<T1, T2>& in_data, Node& out_node)
            {
                /* 1st要素シリアライズ */
                this->Serialize(in_data.first, "first", out_node);

                /* 2nd要素シリアライズ */
                this->Serialize(in_data.second, "second", out_node);
            }

            /* std::pair型デシリアライズ */
            template <typename T1, typename T2>
            void Deserialize(const Node& in_node, std::pair<T1, T2>& out_data)
            {
                /* 1st要素 */
                T1 first{};

                /* 1st要素デシリアライズ */
                this->Deserialize(in_node, "first", first);

                /* 2nd要素 */
                T2 second{};

                /* 2nd要素デシリアライズ */
                this->Deserialize(in_node, "second", second);

                /* std::pairに変換してセット */
                out_data = std::make_pair<T1, T2>(first, second);
            }


            /* std::map型シリアライズ */
            template <typename TKey, typename TValue>
            void Serialize(const std::map<TKey, TValue>& in_data, const string_t& name, Node& out_node)
            {
                /* 名前を指定して子ノードを追加 */
                auto& child_node = out_node.add(name, "");

                /* 全要素を走査 */
                for (const auto& item : in_data)
                {
                    /* 各要素をシリアライズして子ノードに追加 */
                    this->Serialize(item, "item", child_node);
                }
            }

            /* std::map型デシリアライズ */
            template <typename TKey, typename TValue>
            void Deserialize(const Node& in_node, const string_t& name, std::map<TKey, TValue>& out_data)
            {
                /* 名前を指定して子ノードを取得 */
                auto& child_node = in_node.get_child(name);

                /* 子ノードの全要素を走査 */
                for (const auto& item_node_pair : child_node)
                {
                    /* キー要素 */
                    TKey key{};

                    /* キー要素デシリアライズ */
                    this->Deserialize(item_node_pair.second, "first", key);

                    /* 値要素 */
                    TValue value{};

                    /* 値要素デシリアライズ */
                    this->Deserialize(item_node_pair.second, "second", value);

                    /* キー/値を追加 */
                    out_data.emplace(std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(value));
                }
            }

            /* クラス/構造体(class/struct)型メンバシリアライズ */
            template <typename TUPLE, size_t ...I>
            void SerializeTupleImple(Node& out_node, TUPLE&& t, std::index_sequence<I...>)
            {
                /* swallowイディオムでクラス/構造体(class/struct)型メンバのタプルを順次展開し、シリアライズ */
                using swallow = std::initializer_list<int>;

                (void)swallow
                {
                    (void(this->Serialize(std::get<1>(std::get<I>(t)), std::get<0>(std::get<I>(t)), out_node)), 0)...
                };
            }

            /* クラス/構造体(class/struct)型メンバデシリアライズ */
            template <typename TUPLE, size_t ...I>
            void DeserializeTupleImple(const Node& in_node, TUPLE&& t, std::index_sequence<I...>)
            {
                /* swallowイディオムでクラス/構造体(class/struct)型メンバのタプルを順次展開し、デシリアライズ */
                using swallow = std::initializer_list<int>;

                (void)swallow
                {
                    (void(this->Deserialize(in_node, std::get<0>(std::get<I>(t)), std::get<1>(std::get<I>(t)))), 0)...
                };
            }

            /* クラス/構造体(class/struct)型メンバタプルシリアライズ */
            template <typename TUPLE>
            void SerializeTuple(Node& out_node, TUPLE&& t)
            {
                /* クラス/構造体(class/struct)型メンバシリアライズ */
                this->SerializeTupleImple(out_node, std::forward<TUPLE>(t), std::make_index_sequence<std::tuple_size<std::decay_t<TUPLE>>::value>{});
            }

            /* クラス/構造体(class/struct)型メンバタプルデシリアライズ */
            template <typename TUPLE>
            void DeserializeTuple(const Node& in_node, TUPLE&& t)
            {
                /* クラス/構造体(class/struct)型メンバデシリアライズ */
                this->DeserializeTupleImple(in_node, std::forward<TUPLE>(t), std::make_index_sequence<std::tuple_size<std::decay_t<TUPLE>>::value>{});
            }


            /* クラス/構造体(class/struct)型シリアライズ */
            template<class T>
            void Serialize(const T& in_data, Node& out_node, typename std::enable_if<std::is_class<T>::value>::type* = nullptr)
            {
                /* メンバの名前付きタプル取得 */
                auto tuple = cpp_lib::type_traits::DataTypeTraits<T>::GetNamedMembersAsTuple(in_data);

                /* メンバの名前付きタプルから各メンバをシリアライズ */
                this->SerializeTuple(out_node, tuple);
            }

            /* クラス/構造体(class/struct)型デシリアライズ */
            template<class T>
            void Deserialize(const Node& in_node, T& out_data, typename std::enable_if<std::is_class<T>::value>::type* = nullptr)
            {
                /* メンバの名前付きタプル取得 */
                auto tuple = cpp_lib::type_traits::DataTypeTraits<T>::GetNamedMembersAsTuple(out_data);

                /* メンバの名前付きタプルから各メンバをデシリアライズ */
                this->DeserializeTuple(in_node, tuple);
            }


            /* 名前付きオブジェクトシリアライズ */
            template <typename T>
            void Serialize(const T& in_data, const string_t& name, Node& out_node)
            {
                /* ツリーノードに名前を指定して子ノード追加 */
                auto& child_node = out_node.add(name, "");

                /* 入力データをシリアライズして子ノードに追加 */
                this->Serialize(in_data, child_node);
            }

            /* 名前付きオブジェクトデシリアライズ */
            template <typename T>
            void Deserialize(const Node& in_node, const string_t& name, T& out_data)
            {
                /* ツリーノードに名前を指定して子ノード取得 */
                auto& child_node = in_node.get_child(name);

                /* 子ノードからデータをデシリアライズ */
                this->Deserialize(child_node, out_data);
            }

        private:
            /* 文字列型シリアライズ */
            inline void Serialize(const string_t& in_data, Node& out_node)
            {
                /* ツリーノードに値を追加 */
                out_node.put_value(in_data);
            }

            /* 文字列型デシリアライズ */
            inline void Deserialize(const Node& in_node, string_t& out_data)
            {
                /* ツリーノードから要素取得 */
                if (auto value = in_node.get_value_optional<string_t>())
                {
                    /* ツリーノード要素から値を取得 */
                    out_data = value.get();
                }
                /* 要素取得失敗時は例外創出 */
                else
                {
                    THROW_APP_EXCEPTION("Value is nothing");
                }
            }

            /* シリアライズしたツリーノードをテキストに変換し、アーカイブにセット */
            inline void WriteText(const Node& in_root_node, serialization::Archive& out_archive)
            {
                /* 文字列ストリーム */
                std::stringstream ss;

#if TEXT_SERIALIZE_MODE == TEXT_SERIALIZE_MODE_XML
                /* インデントサイズ */
                const int indent = 2;

                /* XML書き込み設定(インデント文字、インデントサイズ、エンコーディング) */
                auto setting = boost::property_tree::xml_parser::xml_writer_make_settings<string_t>(' ', indent, boost::property_tree::xml_parser::widen<string_t>("utf-8"));

                /* XMLとして文字列ストリームに書き込み */
                boost::property_tree::write_xml(ss, in_root_node, setting);
#elif TEXT_SERIALIZE_MODE == TEXT_SERIALIZE_MODE_JSON

                /* JSONとして文字列ストリームに書き込み */
                boost::property_tree::write_json(ss, in_root_node);
#else
#error Invalid Serialize Mode TEXT_SERIALIZE_MODE
#endif

                /* 文字列に変換 */
                string_t in_str = ss.str();

                /* 文字列長を取得 */
                size_t text_len = in_str.length();

                /* バッファサイズ算出(文字列長 + 終端文字(null文字)サイズ) */
                size_t buffer_size = text_len + 1;

                /* シリアライズ後のデータサイズでアーカイブのデータバッファメモリ確保 */
                out_archive.Reserve(buffer_size);

                /* オフセット初期化 */
                size_t offset = 0;

                /* 指定オフセット位置から文字列データ書き込み */
                out_archive.Write(in_str, offset);
            }

            /* アーカイブをテキストに変換し、ツリーノードに変換 */
            inline void ReadText(const serialization::Archive& in_archive, Node& out_root_node)
            {
                /* 文字列ストリーム */
                std::stringstream ss;

                /* アーカイブのデータバッファ(ポインタ)を文字列ストリームに追加 */
                ss << in_archive.GetDataPtr();

#if TEXT_SERIALIZE_MODE == TEXT_SERIALIZE_MODE_XML
                /* 文字列ストリームをXMLとしてツリーノードに読み込み */
                boost::property_tree::read_xml(ss, out_root_node);
#elif TEXT_SERIALIZE_MODE == TEXT_SERIALIZE_MODE_JSON
                /* 文字列ストリームをJSONとしてツリーノードに読み込み */
                boost::property_tree::read_json(ss, out_root_node);
#else
#error Invalid Serialize Mode TEXT_SERIALIZE_MODE
#endif
            }

            /* 浮動小数データ(32bit)を16進数文字列に変換 */
            inline void ToHexString(const float32_t& in_data, string_t& out_data)
            {
                /* 文字列ストリーム */
                std::stringstream ss;

                /* 浮動小数 <=> 整数変換器 */
                cpp_lib::binary::FloatIntConverter<float32_t, uint32_t> converter;

                /* 浮動小数 => 整数変換 */
                uint32_t int_data = converter.ConvertToInt(in_data);

                /* 変換した整数を16進数文字列として文字列ストリームに追加 */
                ss << std::showbase;
                ss << std::setw(4);
                ss << std::hex << int_data;

                /* 文字列に変換 */
                out_data = ss.str();
            }

            /* 浮動小数データ(64bit)を16進数文字列に変換 */
            inline void ToHexString(const float64_t& in_data, string_t& out_data)
            {
                /* 文字列ストリーム */
                std::stringstream ss;

                /* 浮動小数 <=> 整数変換器 */
                cpp_lib::binary::FloatIntConverter<float64_t, uint64_t> converter;

                /* 浮動小数 => 整数変換 */
                uint64_t int_data = converter.ConvertToInt(in_data);

                /* 変換した整数を16進数文字列として文字列ストリームに追加 */
                ss << std::showbase;
                ss << std::setw(8);
                ss << std::hex << int_data;

                /* 文字列に変換 */
                out_data = ss.str();
            }

            /* 16進数文字列を浮動小数(32bit)データに変換 */
            inline void FromHexString(const string_t& in_data, float32_t& out_data)
            {
                /* 16進数文字列を整数に変換 */
                uint32_t int_data = std::stoul(in_data, nullptr, 16);

                /* 浮動小数 <=> 整数変換器 */
                cpp_lib::binary::FloatIntConverter<float32_t, uint32_t> converter;

                /* 整数 => 浮動小数変換 */
                out_data = converter.ConvertToFloat(int_data);
            }

            /* 16進数文字列を浮動小数(64bit)データに変換 */
            inline void FromHexString(const string_t& in_data, float64_t& out_data)
            {
                /* 16進数文字列を整数に変換 */
                uint64_t int_data = std::stoull(in_data, nullptr, 16);

                /* 浮動小数 <=> 整数変換器 */
                cpp_lib::binary::FloatIntConverter<float64_t, uint64_t> converter;

                /* 整数 => 浮動小数変換 */
                out_data = converter.ConvertToFloat(int_data);
            }
        };
    }
}

あとがき

今回はテキスト形式でのシリアライズ処理を実装しました。
次回は、任意のデータ型で実際にシリアライズを行う方法について説明します。