スコープと名前空間 解答ページ | Programming Place Plus 新C++編

トップページ新C++編スコープと名前空間

このページの概要

このページは、練習問題の解答例や解説のページです。



解答・解説

問題1 (確認★)

次のプログラムの実行結果はどうなりますか?

#include <iostream>

namespace n1 {
    int x {5};
    int f()
    {
        return x;
    }
}

namespace n2 {
    int f()
    {
        return n1::f() * 2;
    }
}

int x {100};

int main()
{
    int x {10};

    {
        std::cout << x << "\n";
        int x {20};
        std::cout << x << "\n";

        std::cout << n1::f() << "\n";
        std::cout << n2::f() << "\n";
    }

    std::cout << x << "\n";
}


最初に x の値を出力しようとしています。原則として、その箇所からみて一番近いスコープにある名前を探して、それを使おうとします(本編解説)。{ } で作られるブロックの内側にいるので、同じブロック内が一番近い場所ですが、このブロックに入ってからは x が宣言されていないので、候補に挙がるものはありません(初期値が 20 の変数x は、後ろで宣言しているので、まだ使えません(本編解説))。そのため、ここでの x は main関数の先頭で宣言されている、初期値が 10 の変数x のことなので、10 を出力します。

2つ目の出力も x の値を出力しようとしています。その手前の行で宣言している、初期値が 20 の変数x が一番近い x なので、20 が出力されます。

3つ目の出力は n1::f() を出力しようとしています。n1:: のように、名前空間名とスコープ解決演算子を使って対象を特定しているため、シンプルに、名前空間n1 にある関数f のことです(本編解説)。n1::f関数の本体では、x の値を返しています。n1::f関数では x を宣言していないので、関数の外側のスコープまで見に行きます。一段階外側のスコープは n1 の名前空間スコープであり、ここに初期値が 5 の変数x が宣言されているので、この値を返します。したがって、5 が出力されます。

4つ目の出力は n2::f() を出力しようとしています。さきほどと同様のかたちなので、名前空間n2 の関数f を呼び出します。この関数内では、n1::f() * 2 の値を返します。n1::f() は3つ目の出力と同じことなので 5 を返します。それを 2倍して 10 が返されてきて、出力します。

5つ目の出力は x の値を出力しようとしています。宣言場所だけをみると、ブロック内にある初期値が 20 の変数x が近いようにみえますが、このブロックはすでに } で閉じられているので、その中で宣言された名前は使えません。main関数の先頭で宣言されている、初期値が 10 の変数x が使われ、10 が出力されます。

実行結果はこうなります。

10
20
5
10
10

問題2 (確認★)

次のプログラムの実行結果はどうなりますか?

#include <iostream>

namespace code {
    namespace n1 {
        int x {5};
    }

    namespace n2 {
        int f()
        {
            using n1::x;
            return x;
        }
    }

    int f()
    {
        using namespace n1;

        return x + n2::f();
    }
}

int x {100};

int main()
{
    using namespace code;
    std::cout << f() << "\n";

    using n1::x;
    std::cout << x << "\n";
    std::cout << ::x << "\n";
}


1つ目の出力は f() を出力しようとしています。単純にみれば、この位置からみえる f は存在しませんが、usingディレクティブによって code名前空間が、code:: のような修飾なしで使えるようになっているため(本編解説)、code名前空間内が探索されます。すると、code::f関数を発見できるため、これが呼び出されます。

code::f関数は x + n2::f() の結果を返しています。x の宣言はこの位置から見当たりませんが(後ろで宣言されているグローバルスコープの x は使えません(本編解説))、usingディレクティブで n1 を使えるようにしているため、n1::x が使えるので、5 が得られます。n2::f() のほうは明示的に n2名前空間を指定しているので、そのまま n2::f関数のことです。

n2::f関数は x の値を返しています。やはり x の宣言は見当たりませんが、n1::x を using宣言しているので(本編解説)、n1::xx という名前で使用できます。したがって、この値は 5 です。

よって、1つ目の出力は、5 + 5 の結果である 10 になります。


2つ目の出力では、x を出力しようとしています。この位置からは、グローバルスコープの x の宣言が見えていますが、using宣言された n1::x もあります。using宣言は、現在のスコープに名前を導入する効果なので(本編解説)、グローバルスコープの x よりも、同一スコープに加えられた n1::x のほうが近いところにあることになります。よって、出力されるのは 5 です。

3つ目の出力は ::x を出力しようとしています。スコープ解決演算子を使った場合は、その場所にある名前を使います。::x は名前空間名の指定がないようですが、これはグローバル名前空間のことです(本編解説)。したがって、グローバルスコープで宣言さえrている変数x のことであり、100 が出力されます。

実行結果はこうなります。

10
5
100

問題3 (確認★)

次のプログラムはコンパイルエラーになります。理由を説明してください。

#include <iostream>

namespace n {
    #define X  (100)
}

int main()
{
    std::cout << n::X << "\n";
}


#define はプリプロセスで置換されます(「プリプロセス」のページを参照)。そのため、出力を行う行は、コンパイルされるときには以下のように置換済みです。

    std::cout << n::100 << "\n";

n:100 は文法としておかしいので、コンパイルエラーになります。

このように、マクロを名前空間内で定義しても、名前空間スコープに入ることはありません。原則としてマクロを避けるべきですし(「プリプロセス」のページを参照)、スコープの概念を適切に活用するためにも、constexpr変数を使うようにしましょう。

#include <iostream>

namespace n {
    constexpr auto x = 100;
}

int main()
{
    std::cout << n::x << "\n";
}

実行結果:

100

マクロでないのなら、すべて大文字の名前も避けるようにします(「プリプロセス」のページを参照)。

問題4 (応用★★★)

バイナリエディタのプログラムを次のように修正してください。

  1. 今後、汎用的に使えるように改造していくため、コマンドライン引数を解析する関数を、別のソースファイル・ヘッダファイルに分離する
  2. コードを名前空間に入れる
  3. std:: を何度も書かずに済ませる

最新のバイナリエディタのプログラムは、「コマンドライン引数」のページの練習問題にあります。


コマンドライン引数を解析する関数は、analysis_cmdline_args関数です。現在の作りでは、--lower オプションがあることを前提にしていたり、ヘルプやバージョン情報の出力が、print_help関数や print_version関数によって実装されていることを前提にしていたりするため、バイナリエディタ以外のプログラムにそのまま流用できるようになっていません。この辺りの解決は今後おこなうとして、いったん関数を分離することだけやってみます。

手順としては、analysis_cmdline_args関数の定義を新しいソースファイル(analysis_cmdline_args.cpp)に移動し、宣言をヘッダファイル(analysis_cmdline_args.h)に置きます。argv_to_vector関数も移動していいでしょう。あとはビルドが通らないところを適宜なおしていきます。

// main.cpp
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include "analysis_cmdline_args.h"

// 大文字・小文字
enum class Letter {
    upper,
    lower,
};


// バイト列を標準出力へ出力する
//
// address: 対象のバイト列を指し示すポインタ
// size: 対象のバイト列のバイト数
// letter: 16進数のアルファベットの表示方法
static void print_byte_string(const void* address, std::size_t size, Letter letter);


int main(int argc, char** argv)
{
    // コマンドライン引数を std::vector に詰め替える
    auto cmdline_args = argv_to_vector(argc, argv);

    // コマンドライン引数の解析
    option_t option {};
    switch (analysis_cmdline_args(cmdline_args, option)) {
    case AnalysisCmdlineArgsResult::correct:
        break;

    case AnalysisCmdlineArgsResult::correct_exit:
        std::quick_exit(EXIT_SUCCESS);
        break;

    case AnalysisCmdlineArgsResult::error:
        std::cerr << "コマンドライン引数の指定が正しくありません。\n";
        print_help();
        std::quick_exit(EXIT_FAILURE);
        break;
    }

    // 対象のファイルをオープン
    std::string path {cmdline_args.at(1)};
    std::ifstream ifs(path, std::ios_base::in | std::ios_base::binary);
    if (!ifs) {
        std::cerr << path << "が開けません。\n";
        std::quick_exit(EXIT_FAILURE);
    }

    // ファイルの中身をすべて読み込む
    std::istreambuf_iterator<char> it_ifs_begin(ifs);
    std::istreambuf_iterator<char> it_ifs_end {};
    std::vector<char> bytes(it_ifs_begin, it_ifs_end);

    // バイト列を出力
    print_byte_string(
        bytes.data(),
        bytes.size(),
        (option & option_lower_case) ? Letter::lower : Letter::upper
    );
}

// バイト列を標準出力へ出力する
static void print_byte_string(const void* address, std::size_t size, Letter letter)
{
    auto p = static_cast<const unsigned char*>(address);
    auto remain_byte = size;

    while (remain_byte > 0) {
        std::size_t byte_num {remain_byte >= 16 ? 16 : remain_byte};

        for (std::size_t i {0}; i < byte_num; ++i) {
            std::cout << std::setw(2) << std::setfill('0') << std::hex
                      << (letter == Letter::upper ? std::uppercase : std::nouppercase)
                      << static_cast<unsigned int>(*p) << " ";
            ++p;
        }
        std::cout << "\n";

        remain_byte -= byte_num;
    }
}
// analysis_cmdline_args
#include "analysis_cmdline_args.h"
#include <iostream>

constexpr auto app_name = "binary_editor";
constexpr auto version_str = "1.0.1";

// バージョン情報を出力
static void print_version();


// コマンドライン引数を std::vector<std::string> に格納して返す
std::vector<std::string> argv_to_vector(int argc, char** argv)
{
    std::vector<std::string> vec {};
    for (int i {0}; i < argc; ++i) {
        vec.push_back(argv[i]);
    }
    return vec;
}

// コマンドライン引数を解析する
AnalysisCmdlineArgsResult analysis_cmdline_args(const std::vector<std::string>& cmdline_args, option_t& option)
{
    option = option_none;

    switch (cmdline_args.size()) {
    case 2:
        if (cmdline_args.at(1) == "-h" || cmdline_args.at(1) == "--help") {
            print_help();
            return AnalysisCmdlineArgsResult::correct_exit;
        }
        else if (cmdline_args.at(1) == "-v" || cmdline_args.at(1) == "--version") {
            print_version();
            return AnalysisCmdlineArgsResult::correct_exit;
        }

        break;

    case 3:
        if (cmdline_args.at(2) == "-l" || cmdline_args.at(2) == "--lower") {
            option |= option_lower_case;
        }
        break;

    default:
        return AnalysisCmdlineArgsResult::error;
    }

    return AnalysisCmdlineArgsResult::correct;
}

// ヘルプメッセージを出力
void print_help()
{
    std::cout << "使い方:\n"
              << "> " << app_name << " 対象のファイルパス\n"
              << "> " << app_name << " 対象のファイルパス オプション\n"
              << "> " << app_name << " -h(または --help)\n"
              << "> " << app_name << " -v(または --version)\n"
              << "\n"
              << "-h(--help)    使い方を表示します。\n"
              << "-v(--version) バージョン情報を表示します。\n"
              << "\n"
              << "オプション:\n"
              << "-l (--lower)    16進数のアルファベット部分を小文字で表示します。"
              << std::endl;
}

// バージョン情報を出力
static void print_version()
{
    std::cout << app_name << " version: " << version_str << std::endl;
}
// analysis_cmdline_args.h
#ifndef ANALYSIS_CMDLINE_ARGS_INCLUDED
#define ANALYSIS_CMDLINE_ARGS_INCLUDED

#include <vector>

// コマンドライン解析の結果
enum class AnalysisCmdlineArgsResult {
    correct,        // 正しい指定
    correct_exit,   // 正しい指定だが、プログラムは終了させる
    error,          // エラー
};

// オプション
using option_t = unsigned int;
constexpr option_t option_none = 0;          // オプションなし
constexpr option_t option_lower_case = 0b1;  // 小文字で表示する


// コマンドライン引数を std::vector<std::string> に格納して返す
std::vector<std::string> argv_to_vector(int argc, char** argv);

// コマンドライン引数を解析する
AnalysisCmdlineArgsResult analysis_cmdline_args(const std::vector<std::string>& cmdline_args, option_t& option);

// ヘルプメッセージを出力
void print_help();

#endif

単純に analysis_cmdline_args関数を移し替えてみると、print_help関数と print_vesion関数がないというコンパイルエラーを遭遇するはずです。これが、ほかのプログラムで流用できないといった理由の1つです。ここでビルドを通すため、main.cpp の側に残してある print_help関数と print_version関数を無理やり呼ぼうと考えてはいけません。2つのソースファイル間で互いの関数を呼び合うようなプログラムは、両者が互いに依存しあってしまい、汎用的な作りからますます遠ざかる結果になります。一度依存関係が強く結ばれると、あとから解きほぐすのは難しいです。

ここではいったん、print_help関数と print_version関数も analysis_cmdline_args.cpp に持ってくることにします。これでとりあえず、main.cpp から analysis_cmdline_args.cpp の関数を呼ぶことはあっても、逆方向はなくなります。また、analysis_cmdline_args.cpp の関数を呼ぶといっても、それは analysis_cmdline_args.h に公開された宣言を使うことに限られるようにしましょう。analysis_cmdline_args.cpp にとってだけ必要なものは、内部結合されているべきです。


続いて2つ目の「コードを名前空間に入れる」をやってみます。main.cpp のコードはバイナリエディタ固有なので、beditor名前空間に入れます。binary_editor名前空間でもいいですが、あまり長すぎても面倒なので適当に略すことにします。

analysis_cmdline_args.cpp と analysis_cmdline_args.cpp.h のコードは、今はまだ不完全ですが、汎用的になる予定のコードですから、binary_editor名前空間に入れてはいけません。しかしこれらも、コマンドライン引数を解析するという目的のコード群ですし、汎用的に使うのなら、そのときの本体側のプログラムの邪魔をしないように(名前が衝突しないように)、専用の名前空間に入れるべきものです。こちらは、cmd_args名前空間としておきます。

// main.cpp
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include "analysis_cmdline_args.h"

namespace beditor {

    // 大文字・小文字
    enum class Letter {
        upper,
        lower,
    };


    // バイト列を標準出力へ出力する
    //
    // address: 対象のバイト列を指し示すポインタ
    // size: 対象のバイト列のバイト数
    // letter: 16進数のアルファベットの表示方法
    static void print_byte_string(const void* address, std::size_t size, Letter letter);
}

int main(int argc, char** argv)
{
    // コマンドライン引数を std::vector に詰め替える
    auto cmdline_args = cmd_args::argv_to_vector(argc, argv);

    // コマンドライン引数の解析
    cmd_args::option_t option {};
    switch (cmd_args::analysis_cmdline_args(cmdline_args, option)) {
    case cmd_args::AnalysisCmdlineArgsResult::correct:
        break;

    case cmd_args::AnalysisCmdlineArgsResult::correct_exit:
        std::quick_exit(EXIT_SUCCESS);
        break;

    case cmd_args::AnalysisCmdlineArgsResult::error:
        std::cerr << "コマンドライン引数の指定が正しくありません。\n";
        cmd_args::print_help();
        std::quick_exit(EXIT_FAILURE);
        break;
    }

    // 対象のファイルをオープン
    std::string path {cmdline_args.at(1)};
    std::ifstream ifs(path, std::ios_base::in | std::ios_base::binary);
    if (!ifs) {
        std::cerr << path << "が開けません。\n";
        std::quick_exit(EXIT_FAILURE);
    }

    // ファイルの中身をすべて読み込む
    std::istreambuf_iterator<char> it_ifs_begin(ifs);
    std::istreambuf_iterator<char> it_ifs_end {};
    std::vector<char> bytes(it_ifs_begin, it_ifs_end);

    // バイト列を出力
    beditor::print_byte_string(
        bytes.data(),
        bytes.size(),
        (option & cmd_args::option_lower_case) ? beditor::Letter::lower : beditor::Letter::upper
    );
}

namespace beditor {

    // バイト列を標準出力へ出力する
    static void print_byte_string(const void* address, std::size_t size, Letter letter)
    {
        auto p = static_cast<const unsigned char*>(address);
        auto remain_byte = size;

        while (remain_byte > 0) {
            std::size_t byte_num {remain_byte >= 16 ? 16 : remain_byte};

            for (std::size_t i {0}; i < byte_num; ++i) {
                std::cout << std::setw(2) << std::setfill('0') << std::hex
                          << (letter == Letter::upper ? std::uppercase : std::nouppercase)
                          << static_cast<unsigned int>(*p) << " ";
                ++p;
            }
            std::cout << "\n";

            remain_byte -= byte_num;
        }
    }
}
// analysis_cmdline_args.cpp
#include "analysis_cmdline_args.h"
#include <iostream>

namespace cmd_args {

    constexpr auto app_name = "binary_editor";
    constexpr auto version_str = "1.0.1";

    // バージョン情報を出力
    static void print_version();


    // コマンドライン引数を std::vector<std::string> に格納して返す
    std::vector<std::string> argv_to_vector(int argc, char** argv)
    {
        std::vector<std::string> vec {};
        for (int i {0}; i < argc; ++i) {
            vec.push_back(argv[i]);
        }
        return vec;
    }

    // コマンドライン引数を解析する
    AnalysisCmdlineArgsResult analysis_cmdline_args(const std::vector<std::string>& cmdline_args, option_t& option)
    {
        option = option_none;

        switch (cmdline_args.size()) {
        case 2:
            if (cmdline_args.at(1) == "-h" || cmdline_args.at(1) == "--help") {
                print_help();
                return AnalysisCmdlineArgsResult::correct_exit;
            }
            else if (cmdline_args.at(1) == "-v" || cmdline_args.at(1) == "--version") {
                print_version();
                return AnalysisCmdlineArgsResult::correct_exit;
            }

            break;

        case 3:
            if (cmdline_args.at(2) == "-l" || cmdline_args.at(2) == "--lower") {
                option |= option_lower_case;
            }
            break;

        default:
            return AnalysisCmdlineArgsResult::error;
        }

        return AnalysisCmdlineArgsResult::correct;
    }

    // ヘルプメッセージを出力
    void print_help()
    {
        std::cout << "使い方:\n"
                  << "> " << app_name << " 対象のファイルパス\n"
                  << "> " << app_name << " 対象のファイルパス オプション\n"
                  << "> " << app_name << " -h(または --help)\n"
                  << "> " << app_name << " -v(または --version)\n"
                  << "\n"
                  << "-h(--help)    使い方を表示します。\n"
                  << "-v(--version) バージョン情報を表示します。\n"
                  << "\n"
                  << "オプション:\n"
                  << "-l (--lower)    16進数のアルファベット部分を小文字で表示します。"
                  << std::endl;
    }

    // バージョン情報を出力
    static void print_version()
    {
        std::cout << app_name << " version: " << version_str << std::endl;
    }
}
// analysis_cmdline_args.h
#ifndef ANALYSIS_CMDLINE_ARGS_INCLUDED
#define ANALYSIS_CMDLINE_ARGS_INCLUDED

#include <vector>

namespace cmd_args {

    // コマンドライン解析の結果
    enum class AnalysisCmdlineArgsResult {
        correct,        // 正しい指定
        correct_exit,   // 正しい指定だが、プログラムは終了させる
        error,          // エラー
    };

    // オプション
    using option_t = unsigned int;
    constexpr option_t option_none = 0;          // オプションなし
    constexpr option_t option_lower_case = 0b1;  // 小文字で表示する


    // コマンドライン引数を std::vector<std::string> に格納して返す
    std::vector<std::string> argv_to_vector(int argc, char** argv);

    // コマンドライン引数を解析する
    AnalysisCmdlineArgsResult analysis_cmdline_args(const std::vector<std::string>& cmdline_args, option_t& option);

    // ヘルプメッセージを出力
    void print_help();

}

#endif

#include やインクルードガードなどは、名前空間で囲まないように注意してください。問題3でも触れたとおり、こうしたプリプロセスで置換される命令は、名前空間と組み合わせたときに、思ったような意味にならないことが多いはずです。基本的に、プリプロセスの命令を名前空間に入れる必要はありません。

また、main関数も名前空間に入れてはいけません。main関数が、プログラムの開始位置として認識されるのは、グローバルスコープにある場合に限られます。


最後に「std:: を何度も書かずに済ませる」です。これは、usingディレクティブを使います。過度に影響範囲を拡げないように、ヘッダファイル側には書かないのが基本です。たとえば、汎用的に作られる analysis_cmdline_args.h に using namespace std; を書くと、これをインクルードした側にまで影響が及びます。もし、インクルードする側のプログラムの作者が、usingディレクティブを使わない方針だったとしたら、これは完全に「余計なおせっかい」です。

できれば、関数単位で使うぐらいにまで局所化するといいですが、std名前空間は標準ライブラリのものであり、C++ プログラマーが誰しもそれなりの理解を持っていると考えられるものなので、.cppファイルごとに using namespace std; を書くぐらいのことはしてもいいでしょう(ただし、開発プロジェクトの方針にはしたがうようにしましょう)。

// main.cpp
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include "analysis_cmdline_args.h"

using namespace std;

namespace beditor {

    // 大文字・小文字
    enum class Letter {
        upper,
        lower,
    };


    // バイト列を標準出力へ出力する
    //
    // address: 対象のバイト列を指し示すポインタ
    // size: 対象のバイト列のバイト数
    // letter: 16進数のアルファベットの表示方法
    static void print_byte_string(const void* address, size_t size, Letter letter);
}

int main(int argc, char** argv)
{
    // コマンドライン引数を std::vector に詰め替える
    auto cmdline_args = cmd_args::argv_to_vector(argc, argv);

    // コマンドライン引数の解析
    cmd_args::option_t option {};
    switch (cmd_args::analysis_cmdline_args(cmdline_args, option)) {
    case cmd_args::AnalysisCmdlineArgsResult::correct:
        break;

    case cmd_args::AnalysisCmdlineArgsResult::correct_exit:
        quick_exit(EXIT_SUCCESS);
        break;

    case cmd_args::AnalysisCmdlineArgsResult::error:
        cerr << "コマンドライン引数の指定が正しくありません。\n";
        cmd_args::print_help();
        quick_exit(EXIT_FAILURE);
        break;
    }

    // 対象のファイルをオープン
    string path {cmdline_args.at(1)};
    ifstream ifs(path, ios_base::in | ios_base::binary);
    if (!ifs) {
        cerr << path << "が開けません。\n";
        quick_exit(EXIT_FAILURE);
    }

    // ファイルの中身をすべて読み込む
    istreambuf_iterator<char> it_ifs_begin(ifs);
    istreambuf_iterator<char> it_ifs_end {};
    vector<char> bytes(it_ifs_begin, it_ifs_end);

    // バイト列を出力
    beditor::print_byte_string(
        bytes.data(),
        bytes.size(),
        (option & cmd_args::option_lower_case) ? beditor::Letter::lower : beditor::Letter::upper
    );
}

namespace beditor {

    // バイト列を標準出力へ出力する
    static void print_byte_string(const void* address, size_t size, Letter letter)
    {
        auto p = static_cast<const unsigned char*>(address);
        auto remain_byte = size;

        while (remain_byte > 0) {
            size_t byte_num {remain_byte >= 16 ? 16 : remain_byte};

            for (size_t i {0}; i < byte_num; ++i) {
                cout << setw(2) << setfill('0') << hex
                     << (letter == Letter::upper ? uppercase : nouppercase)
                     << static_cast<unsigned int>(*p) << " ";
                ++p;
            }
            cout << "\n";

            remain_byte -= byte_num;
        }
    }
}
// analysis_cmdline_args.cpp
#include "analysis_cmdline_args.h"
#include <iostream>

using namespace std;

namespace cmd_args {

    constexpr auto app_name = "binary_editor";
    constexpr auto version_str = "1.0.1";

    // バージョン情報を出力
    static void print_version();


    // コマンドライン引数を std::vector<std::string> に格納して返す
    vector<string> argv_to_vector(int argc, char** argv)
    {
        vector<string> vec {};
        for (int i {0}; i < argc; ++i) {
            vec.push_back(argv[i]);
        }
        return vec;
    }

    // コマンドライン引数を解析する
    AnalysisCmdlineArgsResult analysis_cmdline_args(const vector<string>& cmdline_args, option_t& option)
    {
        option = option_none;

        switch (cmdline_args.size()) {
        case 2:
            if (cmdline_args.at(1) == "-h" || cmdline_args.at(1) == "--help") {
                print_help();
                return AnalysisCmdlineArgsResult::correct_exit;
            }
            else if (cmdline_args.at(1) == "-v" || cmdline_args.at(1) == "--version") {
                print_version();
                return AnalysisCmdlineArgsResult::correct_exit;
            }

            break;

        case 3:
            if (cmdline_args.at(2) == "-l" || cmdline_args.at(2) == "--lower") {
                option |= option_lower_case;
            }
            break;

        default:
            return AnalysisCmdlineArgsResult::error;
        }

        return AnalysisCmdlineArgsResult::correct;
    }

    // ヘルプメッセージを出力
    void print_help()
    {
        cout << "使い方:\n"
             << "> " << app_name << " 対象のファイルパス\n"
             << "> " << app_name << " 対象のファイルパス オプション\n"
             << "> " << app_name << " -h(または --help)\n"
             << "> " << app_name << " -v(または --version)\n"
             << "\n"
             << "-h(--help)    使い方を表示します。\n"
             << "-v(--version) バージョン情報を表示します。\n"
             << "\n"
             << "オプション:\n"
             << "-l (--lower)    16進数のアルファベット部分を小文字で表示します。"
             << endl;
    }

    // バージョン情報を出力
    static void print_version()
    {
        cout << app_name << " version: " << version_str << endl;
    }

}
// analysis_cmdline_args.h
#ifndef ANALYSIS_CMDLINE_ARGS_INCLUDED
#define ANALYSIS_CMDLINE_ARGS_INCLUDED

#include <vector>

namespace cmd_args {

    // コマンドライン解析の結果
    enum class AnalysisCmdlineArgsResult {
        correct,        // 正しい指定
        correct_exit,   // 正しい指定だが、プログラムは終了させる
        error,          // エラー
    };

    // オプション
    using option_t = unsigned int;
    constexpr option_t option_none = 0;          // オプションなし
    constexpr option_t option_lower_case = 0b1;  // 小文字で表示する


    // コマンドライン引数を std::vector<std::string> に格納して返す
    std::vector<std::string> argv_to_vector(int argc, char** argv);

    // コマンドライン引数を解析する
    AnalysisCmdlineArgsResult analysis_cmdline_args(const std::vector<std::string>& cmdline_args, option_t& option);

    // ヘルプメッセージを出力
    void print_help();

}

#endif

analysis_cmdline_args.h で using namespace std; を書かないため、analysis_cmdline_args.cpp と analysis_cmdline_args.h で同じことを意味するコードが、一方では std:: が付き、他方では std:: が付かないという状態になります。たとえば、argv_to_vector関数の宣言(ヘッダファイル側)は、

std::vector<std::string> argv_to_vector(int argc, char** argv);

のように std:: を付けていますが、.cpp側にある定義は、

vector<string> argv_to_vector(int argc, char** argv)

のように std:: が消えています。このように名前空間名の修飾がなくても、同じ意味になるようにコードが書けていれば問題ありません。つまり、.cpp側での vectorstring が、曖昧さなく std::vectorstd::string であることが分かれば大丈夫です。


今回は、練習のためと、使用感を確認する意味で、std名前空間に対する usingディレクティブを使用しましたが、今後、新C++編の解説の中ではこの機能は使わないようにします。学習中の読者にとっては、std:: がないと、それが標準ライブラリの中に宣言されているものであることが分かりづらいですし、サンプルコードをそのままコピーアンドペーストしたときに、コンパイルエラーになる可能性が高くなるためです。実戦の中で、usingディレクティブを使うかどうかは、各々で検討してください。


参考リンク



更新履歴




はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る