SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

ラムダ式でステップアップ! C++のプログラムから汎用的なアルゴリズムを切り出し利用してみよう


  • X ポスト
  • このエントリーをはてなブックマークに追加

6. 汎用的なアルゴリズムを適用しようとしてみる

 では、いよいよ元のプログラムでこれらを使ってみます。

 次のようにしたいと思います。

要素の型 Book
コレクション list
抽出条件 IsMatch(タイトルか出版社名に "リ" を含む)
各要素に対してやること Show(コンソールに表示、但し Book の出版社名は出版社名リスト内にあるものを表示)

 ところが、単純にIsMatchやShowを作って、汎用的なアルゴリズムであるAppendIf関数やForEach関数に渡そうとしてもうまくいきません。

 例えば、次のソースコードの場合、IsMatch関数ではsearchWordとpublisherListがスコープ外、Show関数ではpublisherListがスコープ外となり、コンパイルできません。

書籍のリストの中から、タイトルか出版社名に "リ" を含むものをコンソールに表示(コンパイル不可)

Program.cpp
#include <iostream>
#include <list>
#include <vector>
#include <string>
using namespace std;

#include "Book.h"
#include "Publisher.h"
#include "MyCollection.h"
using namespace MyCollection;

class Program
{
public:
    void Run()
    {
        const vector<Publisher> publisherList = {
            Publisher(L"技術評論社"          ),
            Publisher(L"翔泳社"              ),
            Publisher(L"オライリー・ジャパン"),
            Publisher(L"SBクリエイティブ"    )
        };

        const list<Book> bookList = {
            Book(L"4774157155", L"C++ ポケットリファレンス"        , 0),
            Book(L"4798108936", L"C++ の絵本"                      , 1),
            Book(L"4798119768", L"独習 C++ 第4版"                  , 1),
            Book(L"4873110637", L"C++ プログラミング入門"          , 2),
            Book(L"4797376686", L"C++ テンプレートテクニック 第2版", 3)
        };

        const wstring searchWord = L"リ";
        list<Book>    filteredBookList;

        AppendIf(bookList, filteredBookList, IsMatch);
        ForEach(filteredBookList, Show);
    }

private:
    static bool IsMatch(const Book& book)
    {
        // コンパイル エラー! searchWord と publisherList がスコープ外!
        return book.GetTitle().find(searchWord) != wstring::npos ||
               publisherList[book.GetPublisherIndex()].GetName()
                              .find(searchWord) != wstring::npos;
    }

    static void Show(const Book& book)
    {
        // コンパイル エラー! publisherList がスコープ外!
        wcout << L"コード: "     << book.GetCode()
              << L", タイトル: " << book.GetTitle()
              << L", 出版社: "   << publisherList[book.GetPublisherIndex()].GetName()
              << endl;
    }
};

int main()
{
    wcout.imbue(locale("Japanese", locale::ctype));
    Program().Run();
    return 0;
}

 どうすれば汎用的なアルゴリズムを使って記述できるのでしょうか。

7. 関数オブジェクトを使った汎用的なアルゴリズムの適用

関数オブジェクト

 ラムダ式がなかった頃でも、C++では関数オブジェクトというものを使って、この問題を解決することができました。

 上の例の場合、IsMatchやShowの中で、Run関数内のsearchWordやpublisherListが参照できればよいわけです。

 そこで、searchWordやpublisherListをカプセル化したオブジェクトを作り、そのオブジェクトを関数の代わりに渡してやるのです。

 ただし、渡されたオブジェクトは、AppendIf関数やForEach関数の中で関数のように扱われます。()を付けて「呼ばれる」ことになります。

 そこで、オブジェクトにoperator()を用意してやり、その中に関数としての処理を書くようにします。

 これが関数オブジェクトと呼ばれるものです。あたかも関数のように()を付けて「呼ぶ」ことができるオブジェクトのことです。

C++のtemplateの特徴

 AppendIf関数やForEach関数はtemplateとして書かれていますが、C++のtemplateは高性能なマクロのようなもので、template内がtemplate引数で 置き換えられた結果が実際のプログラムになります。 例えば、上記の関数を渡すところに、「関数に見えるもの」を渡してプログラムとして成立するなら、問題なく使うことができます。 実際に関数であるかどうかではなく、関数のように()を付けて実行できる形になれば良いのです。いわゆるダックタイピング(duck typing)です。

書籍のリストの中から、タイトルか出版社名に "リ" を含むものをコンソールに表示(関数オブジェクト版)

 先のプログラムをこの関数オブジェクトを使って書いてみると、例えば次のようになり、無事コンパイルと実行ができるようになります。

Program.cpp
#include <iostream>
#include <list>
#include <vector>
#include <string>
using namespace std;

#include "Book.h"
#include "Publisher.h"
#include "MyCollection.h"
using namespace MyCollection;

class Program
{
public:
    void Run()
    {
        const vector<Publisher> publisherList = {
            Publisher(L"技術評論社"          ),
            Publisher(L"翔泳社"              ),
            Publisher(L"オライリー・ジャパン"),
            Publisher(L"SBクリエイティブ"    )
        };

        const list<Book> bookList = {
            Book(L"4774157155", L"C++ ポケットリファレンス"        , 0),
            Book(L"4798108936", L"C++ の絵本"                      , 1),
            Book(L"4798119768", L"独習 C++ 第4版"                  , 1),
            Book(L"4873110637", L"C++ プログラミング入門"          , 2),
            Book(L"4797376686", L"C++ テンプレートテクニック 第2版", 3)
        };

        const wstring searchWord = L"リ";
        list<Book>    filteredBookList;

        // 関数オブジェクトとして publisherList への参照と searchWord のコピーを内包した
        // IsMatch のインスタンスを渡す
        AppendIf(bookList, filteredBookList, IsMatch(publisherList, searchWord));
        // 関数オブジェクトとして publisherList への参照を内包した
        // Show のインスタンスを渡す
        ForEach(filteredBookList, Show(publisherList));
    }

private:
    // AppendIf 関数に渡す関数オブジェクトのクラス
    // (publisherList への参照と searchWord のコピーを内包する)
    class IsMatch
    {
        const vector<Publisher>& publisherList;
        wstring                  searchWord   ;

    public:
        IsMatch(const vector<Publisher>& publisherList, wstring searchWord)
            : publisherList(publisherList), searchWord(searchWord)
        {}

        // 関数のように呼ばれるための operator()
        // これを用意することで、オブジェクトの後ろに () を付けて「呼ぶ」ことができる
        bool operator()(const Book& book) const
        {
            // オブジェクトが publisherList への参照と
            // searchWord のコピーを内包しているため、問題なく使える
            return book.GetTitle().find(searchWord) != wstring::npos ||
                   publisherList[book.GetPublisherIndex()].GetName()
                                  .find(searchWord) != wstring::npos;
        }
    };

    // ForEach 関数に渡す関数オブジェクトのクラス
    // (publisherList への参照を内包する)
    class Show
    {
        const vector<Publisher>& publisherList;

    public:
        Show(const vector<Publisher>& publisherList)
            : publisherList(publisherList)
        {}

        // 関数のように呼ばれるための operator()
        // これを用意することで、オブジェクトの後ろに () を付けて「呼ぶ」ことができる
        void operator()(const Book& book) const
        {
            // オブジェクトが publisherList への参照を内包しているため、問題なく使える
            wcout << L"コード: "     << book.GetCode()
                  << L", タイトル: " << book.GetTitle()
                  << L", 出版社: "   << publisherList[book.GetPublisherIndex()].GetName()
                  << endl;
        }
    };
};

int main()
{
    wcout.imbue(locale("Japanese", locale::ctype));
    Program().Run();
    return 0;
}

 実行結果は、元々のプログラムと同じです。

実行結果
コード: 4774157155, タイトル: C++ ポケットリファレンス, 出版社: 技術評論社
コード: 4873110637, タイトル: C++ プログラミング入門, 出版社: オライリー・ジャパン
コード: 4797376686, タイトル: C++ テンプレートテクニック 第2版, 出版社: SBクリエイティブ

クロージャー

 上の関数オブジェクトでは、Run関数内の変数への参照またはコピーを内包することによって、コールバックされたときに、それを利用できるようになっています。

 このように、用意されたところでの環境を、呼ばれたときにそのまま利用できる仕組みを、クロージャーと呼びます。

 クロージャーは、C++以外の言語にもよく見られる仕組みで、例えば、C# は匿名メソッドやラムダ式、Javaでは無名クラスやラムダ式を、クロージャーとして利用できます。

 後述しますが、ラムダ式はC++でもクロージャーとしての利用が可能です。

次のページ
8. ラムダ式を使った汎用的なアルゴリズムの適用

修正履歴

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

小島 富治雄(フジヲ)

Microsoft MVP for C# (2005.07~)。 - 注目の MVP - Blog: プログラミング C# - 翔ソフトウェア (Sho's) - Web Site: 翔ソフトウェア (Sho's) - Twitter: Fujiwo 著書: - Windows8〔業務アプリ〕開発読本 (共著)

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/8094 2014/10/14 16:20

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング