PG日誌

各記事はブラウザの横幅を1410px以上にすると2カラムの見出しが表示されます。なるべく横に広げてみてください。

XとYのグリッド構造を全て列挙する foreach 操作を C++ で実装する

画像処理や、ゲーム作成をしていると、結構な頻度でXとYで表される2次元のグリッドを扱う事があると思います。その時に、全グリッドを対象にした処理を書く際に以下のようなループ処理を結構な頻度で書く事になると思います。

// 頻繁に出てくるループ処理
for(int y = 0; y < MAX_Y; y++)
{
    for(int x = 0; x < MAX_X; x++)
    {
        auto _g = grid[y][x];
    }
}

で、こういった記述は完全に定型文なのに繰り返し書くのも面倒なのでテンプレートを用いて foreach 構文で全て列挙できるようにしたいと思います。

制作(確認)環境は以下の通りです。

  • Windows10
  • VisualStudio2017、C++

早速ですが、まず、インターフェースクラスを以下のように定義します。2次元の表のサイズを各々取得するメソッドと、指定した座標の要素の内容を取得するメソッドを用意したクラスを宣言します。

// X と Y の組み合わせを foreach するために必要な操作を表すクラス
template <typename T>
class IForEach2Vec
{
public:
    virtual const int getXSize() = 0;
    virtual const int getYSize() = 0;
    virtual T getItem(const int x, const int y) = 0;

    /* 以下のように書いても良いがインターフェースとして純粋ではなくなり多重継承問題となる場合がある */
    /*
    void foreach(std::function<void(int, int, T)> func)
    {
        for (int y = 0; y < this->getYSize(); y++)
        {
            for (int x = 0; x < this->getXSize(); x++)
            {
                T obj = this->getItem(x, y);
                func(x, y, obj);
            }
        }
    }
    */
};

次に、以下のように上記インターフェースを引数に取って、要素を全部列挙して、主処理はラムダ式の述語で処理する操作を持ったクラスを用意します。

#include <functional>

// ベクトルに関係する汎用操作を提供するクラス
class VecUtil
{
    VecUtil() = default; // おまじない
    ~VecUtil() = default;

public:
    VecUtil(const VecUtil&) = delete; // おまじない
    VecUtil& operator=(const VecUtil&) = delete;
    VecUtil(VecUtil&&) = delete;
    VecUtil& operator=(VecUtil&&) = delete;

    // 全部の要素を列挙する操作
    template <typename T>
    static void foreach(IForEach2Vec<T> &src, std::function<void(int, int, T)> func)
    {
        for (int y = 0; y < src.getYSize(); y++)
        {
            for (int x = 0; x < src.getXSize(); x++)
            {
                T obj = src.getItem(x, y);
                func(x, y, obj);
            }
        }
    }
};

で、foreachしたいデータ構造を持つクラスを以下のように冒頭インターフェースの継承して宣言します。

#include <iostream>

// テスト用のクラス
class Test : public IForEach2Vec<int>
{
public:
    const int getXSize() override
    {
        return 3;
    }

    const int getYSize() override
    {
        return 3;
    }

    int getItem(const int x, const int y) override
    {
        return (x + 1) * (y + 1);
    }
};

実際の使い方は以下の通りです。フリー関数にした方が記述量は減りますが、完全に汎用では無いため、staticなメソッド呼び出しとしています。

int main()
{
    Test test;

    VecUtil::foreach<int>(test, [](int x, int y, int obj)
    {
        std::cout << "(" << x << ", " << y  << ") : " << obj << std::endl;
    });

    // もしくはインターフェースのコメントアウトを解除すると以下のように書ける
    //  → こちらのほうが利用側は直観的
    test.foreach([](int x, int y, int obj)
    {
        std::cout << "(" << x << ", " << y  << ") : " << obj << std::endl;
    });

    system("pause");

    // → コンソール出力内容
    // > (0, 0) : 1
    // > (1, 0) : 2
    // > (2, 0) : 3
    // > (0, 1) : 2
    // > (1, 1) : 4
    // > (2, 1) : 6
    // > (0, 2) : 3
    // > (1, 2) : 6
    // > (2, 2) : 9
    // > 続行するには何かキーを押してください . . .

    return 0;
}

これで、全部が列挙できていることが確認できました。

これを基本形として、3次元のデータを扱うように拡張や、エラーが起きた時は別の処理を受け付けるラムダ式の追加や処理しない条件を追加で渡すこともできるので必要があれば改造することで対応できます。

C++11からC++でも範囲for文 (つまり foreach) が使えます。

余談ですが、C++にもforeachがあります。

もう規格化されてから何年も経過しているので、知っている人も多いと思いますが、C++にも「範囲for文」 という機能です。書き方は以下の通りです。

void foo()
{
    std::vector<int> list { 1, 2, 3, 4 };
    for(auto item : list) // ← 範囲 for 文の記述
    {
        std::cout << item << std::endl;
    }
}

C++では、配列とbegin(), end() 操作を持つ構造を列挙することができます。