cxxopts

軽量なC++オプションパーサーライブラリ。POSIX構文をサポートし、ヘッダーオンリーで使いやすい設計です。

cppclicommand-lineheader-only

フレームワーク

cxxopts

概要

cxxoptsは、軽量なC++オプションパーサーライブラリです。POSIX構文をサポートし、ヘッダーオンリーで使いやすい設計が特徴です。シンプルさと移植性を重視する開発者に支持されており、多くのオープンソースプロジェクトで採用されています。

詳細

cxxoptsはモダンなC++11以降に対応し、最小限の依存関係でコマンドライン引数の解析を行います。GNU getoptライクなAPIを提供しながら、C++らしい型安全性と使いやすさを実現しています。

主な特徴

  • ヘッダーオンリー: 単一のヘッダーファイルをインクルードするだけで使用可能
  • 軽量設計: 最小限のコードでコマンドライン解析を実現
  • POSIX準拠: GNU getoptと互換性のある構文をサポート
  • 型安全: C++の型システムを活用した安全な引数処理
  • 自動ヘルプ生成: オプション定義から自動的にヘルプメッセージを生成
  • 例外ベース: エラー処理に例外を使用した明確なエラーハンドリング
  • C++11対応: モダンなC++機能を活用しつつ、幅広い環境で動作

メリット・デメリット

メリット

  • 簡単な導入: ヘッダーオンリーで依存関係が最小限
  • 軽量: オーバーヘッドが少なく高速動作
  • 親しみやすいAPI: GNU getoptに慣れた開発者には直感的
  • 型安全: コンパイル時に型チェックが行われる
  • 移植性: 様々なプラットフォームで動作
  • 最小限の学習コスト: シンプルなAPIで習得が容易

デメリット

  • 機能の制約: 高度な機能は他のライブラリに劣る
  • サブコマンド未対応: 複雑なCLIアプリケーションには不向き
  • 設定ファイル非対応: 外部設定ファイルの読み込み機能なし
  • カスタマイズ性: ヘルプフォーマットなどのカスタマイズが限定的

主要リンク

書き方の例

基本的な使用例

#include <cxxopts.hpp>
#include <iostream>

int main(int argc, char* argv[]) {
    cxxopts::Options options("MyApp", "シンプルなコマンドラインアプリケーション");
    
    options.add_options()
        ("f,file", "処理するファイル名", cxxopts::value<std::string>()->default_value("default.txt"))
        ("v,verbose", "詳細な出力", cxxopts::value<bool>()->default_value("false"))
        ("h,help", "ヘルプを表示");
    
    auto result = options.parse(argc, argv);
    
    if (result.count("help")) {
        std::cout << options.help() << std::endl;
        return 0;
    }
    
    auto filename = result["file"].as<std::string>();
    auto verbose = result["verbose"].as<bool>();
    
    std::cout << "処理ファイル: " << filename << std::endl;
    if (verbose) {
        std::cout << "詳細モードが有効です" << std::endl;
    }
    
    return 0;
}

複数の引数型を扱う例

#include <cxxopts.hpp>
#include <iostream>
#include <vector>

int main(int argc, char* argv[]) {
    cxxopts::Options options("DataProcessor", "データ処理ツール");
    
    options.add_options()
        ("i,input", "入力ファイル", cxxopts::value<std::vector<std::string>>())
        ("o,output", "出力ファイル", cxxopts::value<std::string>())
        ("t,threads", "スレッド数", cxxopts::value<int>()->default_value("1"))
        ("r,recursive", "再帰処理", cxxopts::value<bool>()->default_value("false"))
        ("f,format", "出力形式", cxxopts::value<std::string>()->default_value("json"))
        ("h,help", "ヘルプを表示");
    
    try {
        auto result = options.parse(argc, argv);
        
        if (result.count("help")) {
            std::cout << options.help() << std::endl;
            return 0;
        }
        
        if (result.count("input")) {
            auto inputs = result["input"].as<std::vector<std::string>>();
            std::cout << "入力ファイル:" << std::endl;
            for (const auto& file : inputs) {
                std::cout << "  - " << file << std::endl;
            }
        }
        
        if (result.count("output")) {
            auto output = result["output"].as<std::string>();
            std::cout << "出力ファイル: " << output << std::endl;
        }
        
        auto threads = result["threads"].as<int>();
        auto recursive = result["recursive"].as<bool>();
        auto format = result["format"].as<std::string>();
        
        std::cout << "スレッド数: " << threads << std::endl;
        std::cout << "再帰処理: " << (recursive ? "有効" : "無効") << std::endl;
        std::cout << "出力形式: " << format << std::endl;
        
    } catch (const cxxopts::OptionException& e) {
        std::cout << "エラー: " << e.what() << std::endl;
        return 1;
    }
    
    return 0;
}

位置引数を含む例

#include <cxxopts.hpp>
#include <iostream>

int main(int argc, char* argv[]) {
    cxxopts::Options options("FileUtil", "ファイルユーティリティ");
    
    options.add_options()
        ("c,command", "実行するコマンド", cxxopts::value<std::string>())
        ("v,verbose", "詳細出力")
        ("d,dry-run", "実際には実行せずに表示のみ")
        ("h,help", "ヘルプを表示");
    
    // 位置引数を設定
    options.parse_positional({"command"});
    options.positional_help("COMMAND [OPTIONS]");
    
    try {
        auto result = options.parse(argc, argv);
        
        if (result.count("help")) {
            std::cout << options.help() << std::endl;
            return 0;
        }
        
        if (!result.count("command")) {
            std::cout << "エラー: コマンドが指定されていません" << std::endl;
            std::cout << options.help() << std::endl;
            return 1;
        }
        
        auto command = result["command"].as<std::string>();
        auto verbose = result.count("verbose") > 0;
        auto dry_run = result.count("dry-run") > 0;
        
        std::cout << "実行コマンド: " << command << std::endl;
        
        if (verbose) {
            std::cout << "詳細モード: 有効" << std::endl;
        }
        
        if (dry_run) {
            std::cout << "ドライランモード: 有効(実際には実行されません)" << std::endl;
        }
        
        // コマンド実行ロジック
        if (command == "list") {
            std::cout << "ファイル一覧を表示中..." << std::endl;
        } else if (command == "clean") {
            std::cout << "クリーンアップ実行中..." << std::endl;
        } else {
            std::cout << "不明なコマンド: " << command << std::endl;
            return 1;
        }
        
    } catch (const cxxopts::OptionException& e) {
        std::cout << "オプション解析エラー: " << e.what() << std::endl;
        return 1;
    }
    
    return 0;
}

エラーハンドリングの例

#include <cxxopts.hpp>
#include <iostream>

int main(int argc, char* argv[]) {
    cxxopts::Options options("SecureApp", "セキュアなアプリケーション");
    
    options.add_options()
        ("u,user", "ユーザー名", cxxopts::value<std::string>())
        ("p,port", "ポート番号", cxxopts::value<int>())
        ("s,ssl", "SSL接続を使用")
        ("t,timeout", "タイムアウト(秒)", cxxopts::value<int>()->default_value("30"))
        ("h,help", "ヘルプを表示");
    
    try {
        auto result = options.parse(argc, argv);
        
        if (result.count("help")) {
            std::cout << options.help() << std::endl;
            return 0;
        }
        
        // 必須パラメータのチェック
        if (!result.count("user")) {
            throw cxxopts::OptionException("ユーザー名が必要です");
        }
        
        if (!result.count("port")) {
            throw cxxopts::OptionException("ポート番号が必要です");
        }
        
        auto user = result["user"].as<std::string>();
        auto port = result["port"].as<int>();
        auto ssl = result.count("ssl") > 0;
        auto timeout = result["timeout"].as<int>();
        
        // 値の検証
        if (port < 1 || port > 65535) {
            throw cxxopts::OptionException("ポート番号は1-65535の範囲である必要があります");
        }
        
        if (timeout <= 0) {
            throw cxxopts::OptionException("タイムアウトは正の値である必要があります");
        }
        
        std::cout << "接続設定:" << std::endl;
        std::cout << "  ユーザー: " << user << std::endl;
        std::cout << "  ポート: " << port << std::endl;
        std::cout << "  SSL: " << (ssl ? "有効" : "無効") << std::endl;
        std::cout << "  タイムアウト: " << timeout << "秒" << std::endl;
        
    } catch (const cxxopts::OptionException& e) {
        std::cerr << "エラー: " << e.what() << std::endl;
        std::cerr << std::endl << options.help() << std::endl;
        return 1;
    } catch (const cxxopts::argument_incorrect_type& e) {
        std::cerr << "型エラー: " << e.what() << std::endl;
        return 1;
    } catch (const std::exception& e) {
        std::cerr << "予期しないエラー: " << e.what() << std::endl;
        return 1;
    }
    
    return 0;
}

CMakeでの使用例

# CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
project(MyCxxoptsApp)

# cxxoptsを取得(FetchContentを使用)
include(FetchContent)
FetchContent_Declare(
    cxxopts
    GIT_REPOSITORY https://github.com/jarro2783/cxxopts.git
    GIT_TAG v3.1.1
)
FetchContent_MakeAvailable(cxxopts)

add_executable(my_app main.cpp)
target_link_libraries(my_app cxxopts::cxxopts)
target_compile_features(my_app PRIVATE cxx_std_11)