Level 3:関数 3.5 デフォルト実引数 |
※資料は自著より引用
■3.5 デフォルト実引数
●汎用性の高い引数の意味的な分類
様々な関数の引数を分析すると,
(1)
関数の第1引数をはじめより左側(最初の方)の引数は処理対象の情報を指定するより重要な引数
(2) 右側の方の引数になるにつれて,付加情報や細部の挙動を指定するオプショナルな引数
となる。このことは前項で定義したmySort関数にも言える(下List 3-11, 再掲)。
●引数の省略に対する要求
上記(2)に相当する引数の実引数の値には偏りがあることが多い。mySort関数の場合も,第3引数に
指定する実引数は昇順を示す kAscending が使用される場合が圧倒的に多いはずである。このような
引数に関して,「実引数を省略して呼び出したい。実引数が省略されたときは特定のデフォルト値が
していされたものとして扱って欲しい。」という要求がでるのが常である。mySort関数の例で言えば,
「第3実引数を省略して呼び出したら,第3実引数にkAscendingが指定されたものとみなして昇順で
整列して欲しい」という事である。これを実現するのがC++で追加されたデフォルト実引数(default
argument)である。
デフォルト実引数の指定の仕方を Fig. 3-11 に示す。デフォルト実引数は,関数宣言内の仮引数の宣言
を行う際に,まるで仮引数に初期値を指定しているかのように = で指定する。そして,デフォルト実引数
を指定した実引数は関数呼び出し時に省略することができる。
では,実際に使用例を見てみましょう。
●デフォルト実引数の役割
List 3-12は,関数mySortの第3仮引数にデフォルト実引数として kAscending を指定した例である(12行目)。
43行目で,要素数kEementNumの配列iArrayを昇順に整列させるために
mySort( iArray, ::kElementNum );
というように第3実引数を省略して呼び出している。このとき,省略された第3実引数の値として指定されたデフォルト
実引数 kAscending が用いられることになる。
50行目では,降順に整列させるため,第3実引数を省略せずに kDescending を実引数として渡している。
このように,デフォルト実引数を用いると,ひとつの関数定義であた
・与えられた配列を昇順に整列する関数
・与えられた配列を昇順にも降順にも整列させることができる関数
の2バージョンが存在しているようにmySort関数を利用できる。そして,多くの場合は実引数が少ない方を
呼び出せば良いので「楽が出来る」というわけである。
●デフォルト実引数の注意点
なお,デフォルト実引数は必ず最後の仮引数から左に向かって順に指定していかなくてはいけない。そして,
関数呼び出し時に省略できる実引数は,最後の実引数から左に向かって順に省略可能になる。(下図 Fig.3-11, 再掲)
下List 3-13 の関数fooでは,最後の仮引数である第3引数にデフォルト実引数を指定していないのに,
第2仮引数にデフォルト実引数を指定しているのでエラーになる。
●関数定義におけるデフォルト実引数
Fig. 3-11にあるように,デフォルト実引数は関数定義ではなく,関数宣言で指定すること。下図に推奨の
方法を示す。
ところで,以前学習したように,定義も宣言の一種である。そうであるなら,下 List 3-14 のように関数定義内
でデフォルト実引数を指定しても良いはずである。
確かにそうなのだが,これはあまりお勧めできない。理由を如何に示す。興味がある物は読んで観よ。
●関数をライブラリモジュールとして独立させるときに起こる問題
問題は,ヘッダファイルを作る,つまりモジュール化を行う局面である。まず,List 3-14 をソースファイルと
ヘッダファイルに分けるとき,下List 3-15, 3-16 のようにしてしまったとする。この例では,List 3-15 の
関数プロトタイプ宣言にデフォルト実引数の指定を忘れてしまっている。このヘッダファイルを読み込んで
利用しようとしている List 3-17 では,ヘッダファイル(List 3-15)を読み込んだだけでは,関数fooにデフォルト
実引数が指定されていると知ることができないので,コンパイルエラーとなる。
エラーが出てしまう例:List 3-15〜17
では,上記のエラーを解決しようとしてヘッダファイル List 3-15 の関数プロトタイプ宣言にデフォルト実引数の指定を
int foo( int a = 1, int b = 2, int c = 3 ); …(※)
と言う具合に追加してみよう。実はこのようにList 3-15を変更しても,List 3-15〜17 の組み合わせでは別のエラーが起こる。
これは,List 3-15 の関数プロトタイプ宣言(※)に含まれるデフォルト実引数の指定と,List 3-16 の関数定義に含まれる
デフォルト実引数の指定がバッティングしてしまうためである。実は,デフォルト実引数の指定は,たとえまったく同じ内容
でも2重に行われるとエラーになるのである(下図 Fig. 3-12)。
結局,
・
List 3-15の関数プロトタイプ宣言にデフォルト実引数指定をつける(下 List 3-18)
・
List 3-16の関数定義に含まれる宣言にデフォルト実引数指定を削除する(下 List 3-19)
という作業を行う必要がある。List 3-17〜19の組み合わせで,エラーが無くなる。この形は,結局前述
した推奨方法と同じになる。
改善例:List 3-18〜19
●関数の拡張とデフォルト実引数
デフォルト実引数は,既存の関数を拡張する場面でも有効である。たとえば,整列を行う関数mySortの古い
バージョンが,以下の様に配列引数と配列の要素数の2個しか引数を取らない形だったとする。
void mySort( int a[ ], size_t element_num ) { /* 省略 */ }
この古いバージョンを書き換えて第3の引数を追加し,List 3-11 のよう定義しようと思い立った時点で,
既存のソースコードには
mySort( iArray, ::kElementNum );
…(★)
というように,古い引数2個のバージョンのmySortを呼び出している箇所が何カ所もある可能性がある。
もしデフォルト実引数を使わずに,関数mySortを3つの引数を持つ新バージョンに書き換えたら,このような
古いバージョンの呼び出し(★)箇所すべてで,第3の実引数を追加しなくてはならないことになる。
しかし,List 3-12 のように,追加する第3仮引数にデフォルト実引数を指定してやれば,上記の古いバージョン
の呼び出し部分(★)は変更せずにすむことになる。
(ただし,古いバージョンはあくまで実引数を2つだけ渡して関数mySortを呼び出すようにコンパイルされる
のに対し,List 3-12の仮引数を3個持つ新バージョンの関数mySortを(★)というように呼び出しているものは
やはりあくまで実引数を3つ渡すようにコンパイルされるので,バイナリ上での互換性は無い。つまり,古い
(★)の呼び出しが書かれているソースはコンパイルしなおす必要がある。)