2014. 5.20.
2014. 6. 1. クラスのコンストラクタに初期化リスト(initialization list)を使う
石立 喬

Visual Studio Express 2013 の新しい使い方(4)

――― コンソールアプリケーションで、generateとラムダ式を使う ―――

 コンソールアプリケーションは、応用面での便利さは劣るものの、言語本来の学習には向いている。 ISO C++11の規格がVisual Studio Express 2013に本格的に取りいれられているので、新しい機能の使用に努める。
 ここでは、generateとrange-based forを使って、三角関数表を作成するプログラムを紹介し、併せて他の方法の例も示す。
 今まで、数表の作成は、何回も扱ってきた。最近では、「Visual Studio 2012 Express の易しい使い方(4)」で、ごく基本的な方法で、三角関数表を作成している。これと、比較していただきたい。

ここで使用する新しい言語機能の簡単な説明
 本サイトは、従来から、文法よりも理工系技術者が関心を持つ応用に重点を置き、文法の解説は他のサイトや書籍に頼ってきた。この方針は現在でも変わらないが、ここで初めて取り上げた機能を簡単に説明する。
◎初期化リスト(initialization list)によるコンストラクタの作成
 書式は次のようになっており、:(コロン)の後に、設定したいメンバー変数の数だけ、,(コンマ)で区切って並べる。{ } は必須で、普通は空白のままにしておくが、コンストラクトと同時に実行させたいコードを書くことができる。最後に;(セミコロン)は付けない。

 <クラス名>(引数とデフォルトの初期設定):<メンバー変数名1>(引数を含む式)、<メンバー変数名2>(引数を含む式)、・・・、{ }

 メンバー変数がconstや参照であっても設定できるところが便利である。
◎generate
 名前空間stdに含まれているstd::generate関数テンプレート(テンプレート関数とも、英語では、function templateが多く使われるが、templated functionもある。)である。扱う対象のvectorが、任意の型を扱うことができるテンプレートなので、これもテンプレートになっている。ヘッダ<algorithm>を必要とする。引数は、
   開始イテレータ  first
   終了イテレータ  last
   ジェネレータ    gen
であり、戻り値はない。
 ジェネレータは、呼び出されるたびに次の値を返す関数オブジェクトで、引数はとらない。
◎generate_n
 generate_nと同じ作用をするが、引数が異なる。
   出力イテレータ  first  (generateの開始イテレータと同じ)
   個数         n
   ジェネレータ    gen  (generateのジェネレータと同じ)
◎ラムダ式(lambda expression)について追加説明
 「新しい使い方(1)」で、簡単に説明したが、ここでは、さらに詳しく説明する。
 基本的な書式は、
   [@キャプチャの指定 ] (A引数の指定 ) B mutable C ->戻り値の指定 { Dラムダ式の本体 };
から成る。
 @キャプチャの指定 --- ラムダに取り込む外部スコープの変数を、値で(by value)か、参照で(by reference)かを指定する。
   [ ] --- キャプチャしない。外部のスコープの変数を取り込まない。
   [=] --- 外部変数を、すべて値渡しで受け取り、コピーして使う。ラムダ内部での変更はできない。参照元に影響を与えない。
   [&] --- 外部変数を、すべて参照渡しで受け取り、ラムダ内部で変更ができる。参照元に影響を与える。[const &]はない。
  変数ごとに渡し方を変える場合は、まず原則を示して、次いで、例外を指定する。[&, a, b]など。
 A引数の指定 --- 括弧だけで中身を記述しなかったり、括弧そのものを省略することができる。
 Bmutable(変更可能の指定)--- 値で受け取った変数を、ラムダ内部で変更したい(参照元には影響を与えない)ときに記述する。必要のない時は書かない。
 C -> 戻り値の型指定 --- コンパイラの推論に任せられない場合に使用する。ラムダ本体にreturn文が1個のみの場合や、値を返さない場合には不要。ここで戻り値の型を指定した場合には、Aの( ) は省略できない。
 Dラムダ式の本体 --- ここに関数の内容を書く。戻り値がある場合は、return文が必要になる。

プログラム
 このプログラムは、角度(0度〜360度)に対して、そのサインとコサインを表示する三角関数表を作成する。従来の手法と趣向を変えて、角度、サイン値、コサイン値をTrigonと命名するクラスにまとめ、一括して処理する。コンストラクタには、初期化リストを使用した。このクラスが引数なしで呼び出された場合には、デフォルトの条件、angle = 0 が適用され、1個の引数を与えた場合には、それを優先して、初期化リストに従って各メンバー変数を設定する。
 ヘッダ部分とTrigonクラスの宣言部分を図1に示す。


図1 ヘッダのインクルードと、作成したTrigonクラス


 図2は、実行部分のメイン関数を示したもので、関数テンプレートstd::generateを用いて、Trigon型データtrigon_dataを13個作成してvector化している。generateの第3引数のジェネレータはラムダ式で作成した。ラムダ式で、[&]は、trigon_dataを参照渡しで受け取るために必要なので、「&」を省略することはできない。
 ラムダ式の本体について説明すると、最初にジェネレータが呼び出された時は、static int i = 0; によって変数iが0に初期化される。二回目以降の呼び出しに対しては、staticなので、再初期化されることはない。
 数表作成には、範囲ベースのforを用いた。vectorのtrigon_arrayから、Trigon型の要素を1個ずつ参照で受け取り、それをtdとして、メンバ変数を表示する。


図2 mainプログラム


得られた結果
 図3は、プログラムを実行した結果である。


図3 実行結果


initialization listを使わない、従来の方法によるTrigonクラスのコンストラクタ
 図4に従来の代入式による方法を示す。こちらの方が、分かり易いかもしれない。


図4 代入方式による初期設定


trigon_arrayを生成するその他の方法
 trigon_arrayを生成するには、他にも色々な方法がある。これらを以下に紹介するが、特段、これでないといけないものはない。
◎generate_nを使用する
 generateの代わりに、generate_nを使うこともできる。これは、要素数を明示的に示すもので、generateの場合の終了イテレータの代わりに、個数を与える。vectorは、本来要素数を意識しないで使うものなので、この場合のように要素の全てに作用させる際には、generate_nを使う理由はない。


図5 generate_nを使った場合


◎通常のforとpush_backを用いる
 vectorを使うのであれば、push_backを使うのがオーソドックスではないか、との意見もあろう。その通りでもあるので、その場合のコードを、図6に紹介する。ついでに、オーソドックスな通常のforを使った。これが一番分かり易いかもしれない。


図6 通常のforでpush_backを使った場合


◎range-based forを用いる
 trigon_arrayの要素を、順次参照渡しでtdとして受け取り、それにSetDataでデータを設定する。Trigonクラスの指定の代わりに、コンパイラに推論を求めるautoを用いてみた。


図7 range-based forを使った場合


◎for_eachを用いる
 ラムダ式のキャプチャ句[ ] は、最初、[&]にしてあったが、その必要がないことが分かった。trigon_arrayの各要素は、外部変数としてのキャプチャではないと考えられる。ラムダ式への引数の指定欄ではTrigon& tdを用いて、内部で変更できるようにした。


図8 for_eachを使った場合