SHOEISHA iD

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

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

Modern C++入門

関数ポインタとおさらば! Modern C++のラムダ式

第5回 無名関数を使いこなす

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

 本連載では、Modern C++と称されるC++について、Modern C++らしい言語仕様をピックアップし紹介していきます。第5回は、無名関数とも呼ばれるラムダ式を紹介します。50年以上前にプログラミング言語Lispに導入され、他言語では当たり前になっているラムダ式が、C++でも使えるようになりました。関数へのポインタという難解な仕様とはおさらばできる、ラムダ式とキャプチャについて理解を深めます。

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

はじめに

 C言語から派生したオブジェクト指向プログラミング言語であるC++は、21世紀に入ってまったく別物とも言えるプログラミング言語に成長していきました。それは、Modern C++と称されています。1990年代にC++を触っていたプログラマが現在の仕様を知れば、隔世感に苛まれるのではないでしょうか。本連載では、かつてはC++をたしなんでいたという方、今からC++言語を始めるという方に向けて、Modern C++らしい言語仕様をピックアップし紹介していくことで、今のC++言語の姿を理解していただきます。

対象読者

  • かつてはC++をたしなんでいたという方
  • 今からC++言語を始めるという方
  • モダンなプログラミング言語のパラダイムに興味のある方

必要な環境

 本記事のサンプルコードは、以下の環境で動作を確認しています。

  • macOS Sonoma / Windows 11
    • Xcode Command Line Tools 2395
    • MinGW GCC 9.2.0

ラムダ式って何よ?

 20世紀のC++プログラマ(筆者)が、今(21世紀)のC++を知ってあっと驚く連載の第5回です。ムーブセマンティクスという山を越えたので、あとは楽になる一方だと楽観しています。そこでラムダ式です(単にラムダと呼ぶこともあるようです)。C++を最近はやっていなくても、他のプログラミング言語を触っていれば耳にしたことのある言葉ですね。知らなくても、大昔にLispとか触ったことがあれば、もしかしてアレに関係あるのかな?と思ったあなたは鋭いです。今回は、このラムダ式を中心に書きます。

[NOTE]C++プログラムのコンパイル

 以降のサンプルは、基本的に「gcc -o 出力先ファイル名 ソースファイル名」でコンパイルできますが、環境によってはライブラリやC++バージョンの指定が必要になることがあります。コンパイルやリンクでエラーが発生するときには、以下のようにライブラリの指定(-lstdc++)、C++バージョンの指定(-std=c++11)の追加を検討してください。

% gcc -o sample sample.cpp -lstdc++ -std=c++11	// c++14, c++17, c++20など

まずは関数のポインタから

 C言語からたしなんでいれば、関数のポインタというものを使ったことはあると思います。普通のポインタだけでも分からないのに関数のポインタなんて!と筆者も若いときにはそう思いましたが、今となってはなんて甘っちょろかったのだろうと自責の念にとらわれています。関数のポインタ、以下のリストみたいに宣言して使います(例によって#includeやnamespaceはスペースの都合で割愛しているので、全体は配布サンプルを参照してください)。

リスト function_ptr.cpp
int func() { return 0; }	// 関数
…略…
int (*fp)() = &func;		// 関数ポインタ
(*fp)();			// ポインタを使った関数呼び出し

 なんてことはなく、関数ポインタとはポインタ演算子と識別子をカッコで囲んで(こっちをカッコで囲むのは優先順位の問題です)、関数であることを示すカッコを付けて宣言するだけですね。そして初期値は関数の名前にアドレス参照演算子を付けると、簡単です。とはいえ、使いどころに悩むのも事実です。自身のプロジェクトで、関数のポインタにあまり活躍させると破綻への道まっしぐらなので、ここはライブラリ関数に登場してもらいましょう。よく知っているqsort関数です。クイックソートを実行する標準ライブラリの関数ですが、並び換えの際の順序比較を外部関数に行わせる仕様になっているんですね。qsort関数のプロトタイプ宣言はこんな感じです。

void qsort(void *base, size_t num, size_t size, 
           int (*compare)(const void *, const void *))

 baseをsizeの大きさでnum要素ある配列と見なし、最終引数のcompare関数(ポインタ)で比較して並び替える、というものです。今ならテンプレートとか使ってスマートにやるのでしょうが、C言語なので。筆者的にはこういう内部まる見えの感じは好きですが。これを呼び出すコードは、以下のリストのようになります。

リスト function_ptr.cpp
// 比較関数(a > bなら正、a < bなら負、a == bなら0が返る)
int compare_func(const void *a, const void *b) {
    return *(int *)a - *(int *)b;
}
…略…
int array[] = {9, 6, 8, 4, 2, 5, 1, 10, 3, 7};
qsort(array, sizeof(array) / sizeof(int), sizeof(int), compare_func);
for( int i = 0; i < sizeof(array) / sizeof(int); i++ ) {
    cout << array[i] << endl;
}

 並び替えにあたり、配列arrayの各要素の大小をcompare_func関数が比較して、0または正負の値で返す、という感じです。qsort関数自体は、どのような比較が行われているのは知るよしもなく、その結果に従って淡々と並び換えを実行するのです。逆を言えば、どんな並び替えもcompare_func関数次第で可能(昇順と降順の切り替えとか、あるいは未知の比較方法でも)ということで、関数の汎用性を上げることができます。

 関数ポインタってこんな感じです。思い出しましたか? ちょっと危ないのは、compare_func関数へは各要素のサイズを渡すことができないので、どのようなサイズを想定して比較しているかも完全にお任せだということでしょう。

関数オブジェクトを定義するラムダ式

 しかしながら、上記リストのコードは冗長ではないですか? qsort関数に渡すためだけの関数を名前まで付けて定義しているので、結構ラムダ……じゃなかった、ムダな感じがします。また、関数定義が離れたところにあるのも可読性という点ではイマイチな気がします。

 そこで、関数そのものをオブジェクトとして扱えたら便利じゃない? という発想が各所で生まれました(生まれたというより元からあったんですが、それを手続き型のプログラミング言語に持ってこようという発想ですね)。

 これが、C++ 11で導入されたラムダ式(lambda expressions)です。ラムダ式は、簡易的な関数オブジェクトをその場で定義できる機能、とあります。簡易的というと、その場限りとか、そういう意味でしょうね。イベントハンドラやqsortに渡す関数なんかは、その場限りというか相手が限定されますから、その場その場で定義できる方が便利でしょう。

[NOTE]なぜラムダ?

 ラムダ式という名称は、ある数学者の考案したラムダ計算から来ていると言われます。ラムダ計算のモデルでは、関数を表すのにラムダ文字(λ)を使ったからとも言われます。Lisp言語でlambdaを使うのもここから来ているようです。なので、根っこは一緒のようです。

 ラムダ計算では、例えば関数f(x)=x+1は、λx.x+1と表現されるようです(「ようです」が続くのは、筆者が数学を大の苦手としているからです)。しかし、これってずいぶんとなじみのある表現のような…、他言語でラムダとか匿名関数とかかじったなら、ピンと来そうな表現ですね。

高階関数と第一級関数

 ついでですが、ここで高階関数についても紹介します。やだなぁ、こういうの。高層マンションの上の方に住んでいる関数?みたいな印象があって、あまり良い印象がないのですよね(偏見)。もちろん、そんな意味ではなく、高階関数とは「関数そのものを引数で受け取ったり、関数そのものを戻り値として返す関数」のことです。さらに、第一級関数(first-class function)という概念もあります。高階関数は、第一級関数をサポートするプログラミング言語で、さらに上記の条件を満たす関数、とちょっと複雑な話になってきました。第一級関数とは、オブジェクトと見なすことができる関数、ということです。なので、関数ポインタはあるがオブジェクトと見なすことはできない(そもそもオブジェクトという考え方がない)C言語などでは、第一級関数は存在しないということになります。必然的に高階関数もないというわけです。

 高階関数は、関数型プログラミング言語では、普通に実装されているということになっています。

会員登録無料すると、続きをお読みいただけます

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

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

メールバックナンバー

次のページ
ラムダ式を使ってみる

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

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

もっと読む

この記事の著者

WINGSプロジェクト 山内 直(WINGSプロジェクト ヤマウチ ナオ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook <個人紹介>WINGSプロジェクト所属のテクニカルライター。出版社を経てフリーランスとして独立。ライター、エディター、デベロッパー、講師業に従事。屋号は「たまデジ。」。

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

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

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

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

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/19112 2024/03/14 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

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

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

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

メールバックナンバー

アクセスランキング

アクセスランキング