13.2 多態性 ( ポリモルフィズム ) と仮想関数 235 virtual を付ける必要もありません。ただし、新しい core クラスの定義のコードはコンパイルし直す必要があ ります。そうすると、基底クラスの関数で virtual と指定したことになり、コードは望んだ振る舞いをするよ うになります。 13.2.2 動的結合 virtual な関数 ( 仮想関数 ) のこのような実行時の選択は、参照かポインタを通して関数が呼ばれるときだけ 行われます。仮想関数であっても、 ( 参照やポインタではなく ) オプジェクトに対して実行されるなら、その型 はコンパイル時に決まっているのであり、実行時に変わることはありません。一方、参照やポインタは基底クラ スでも派生クラスでも指すことができ、したがって、それらの指すオプジェクトの型は実行時に決められること になります。 virtual の仕組みが異なる結果を出すのはこのような場合なのです。 たとえば、 compare-grades を次のように書いたとします。 / / 誤った実装 b001 compare—grades (Core cl , Core c2) return cl . grade ( ) く c2. grade() ; パラメータはオプジェクトであって参照ではありません。この場合、 c 1 と c2 の型はいつも core とわかってい るのです。このような定義でも引数に Grad オプジェクトを使うことができます。しかし、このとき、 Grad の型 は使われません。その場合、オプジェクトの core 部分だけが渡されるのです。つまり、 Grad オプジェクトの core にない部分は、切り落 (cutdown) とされてしまい、 core の部分だけがコピーされて compare-grades に渡されることになります。パラメータが core オプジェクトであるため、この関数内の grade 呼び出しは、 ( コンパイル時に ) core::grade になります。このようにコンパイル時に呼び出される関数が決められているこ とを、静的結合 (statically bound) と言います。 参照やポインタを使う関数呼び出しの場合は動的結△ , 、ロロ (dynamic binding) とよばれますが、これと静的結合 の違いは、 C+ + がどのように OOP をサポートするかを理解する上で、非常に重要です。静的結合という言葉が 「呼び出す関数がコンパイル時に決められる」を意味するのに対し、動的結合という言葉は「関数の呼び出しが 実行時に決められる」という意味です。仮想関数であっても、オプジェクトに対して呼び出すなら、その呼び出 しは静的結合です。そのオプジェクトがコンパイル時と実行時に異なる型になることはないからです。一方、ポ インタや参照に対しては、仮想関数は、実行時に内容が決められる、動的結合になります。仮想関数は、参照や ポインタが実行時に指す型によって、異なるバージョンのものが使われるのです。 Core& r = g; Core* p; Grad g; Core C ; c . grade(); g. grade(); p->grade ( ) r ・ grade(); / / core: : grade ( ) に静的に結合 / / Gore: : grade ( ) に静的に結△ ; / / 動的な結合 : p の指すオプジェクトの型によって異なる / / 動的な結合 : r の指すオプジェクトの型によって異なる
13.6 微妙な点 249 もし、 vector く core> オプジェクトを作って、そこに core オプジェクトと Grad オプジェクトを格納しようと すると、さらに驚くかもしれません。それはできるのです。しかし、その結果が驚くべきものです。たとえば、 vector く Core> students ; Grad g(cin) ; students . push—back (g) ; / / Grad を読み込む / / g の core 部分 ( ! ) を students に格納 core オプジェクトの参照が予想される場所で Grad オプジェクトを使うことができるので、 students に Grad オプジェクトを格納することができます。 push-back 関数は、 vect 。 r の要素の型の参照を引数に取るので、 g を push-back に渡すことができるのです。しかし、このオプジェクトを students に格納しようとすると、そ の c 。 re の部分だけがコピーされてしまうのです。最初に見たときは驚くかもしれませんが、毯 13.2.2 で見たよ この振る舞いはもともと予想されるものです。 students に格納された要素は core オプジェクトとして 振る舞うでしよう。 vect 。 r は g の core 部分だけをコピーして core オプジェクトを生成し、それを students に格納するのです。このとき、 Grad の他の部分は捨てられてしまいます。 13.6.2 どの関数を使いたいのか 基底クラスと派生クラスが同じ名前のメンバを持っていながら、それらのパラメータの型や数が異なると、そ れらは完全に無関係の関数として振る舞うことに注意してください。たとえば、学生の最終成績を変更できるア クセス関数を付け加えたとしましよう。 c 。 re ではこの関数は単に fi Ⅱ al を設定するだけの関数とし、 Grad で は 2 つの引数を持つ関数とします。そして、その第 2 引数は thesis の設定値とします。 void core : :regrade(double d) { final = d; } void Grad: :regrade (double dl , double (2) { final = dl ; thesis = d2; } ここで r を core の参照とすると、次のようになります。 r. regrade(100) ; r. regrade(100, 100 ) ; / / OKO core : : regrade の呼び出し / / コンパイルエラー。 core: : regrade は引数を 1 つしか取らない 2 行目の関数呼び出しは、 r が実際には Grad オプジェクトを指していてもエラーになります。 r の型は Core の 参照なので core の regrade が呼ばれるのですが、これは double の引数を 1 っ取るだけだからです。 もっと驚くことは、 r が Grad の参照の場合でしよう。 て . regrade(100) ; r. regrade(100, 100 ) ; / / コンパイルエラー。 Grad: : て egrade には引数が 2 つ必要 / / OKO Grad: : て egrade の呼び出しになる 今度は Grad オプジェクトを指す r について実際に呼び出す関数を探すことになるのですが、 Grad オプジェク トに対しては、 regrade 関数は 2 つの引数を取る関数です。引数が 1 つの同名の関数が基底クラスにあります が、これは派生クラスの regrade によって隠されてしまうのです。もし基底クラスの関数を呼び出したいなら、 次のように関数名を明示しなければならないのです。 / / OKO core : : regrade の呼び出し て . CO て e : :regrade(100) ;
236 第 13 章継承と動的結合を使う はじめの 2 つの関数呼び出しは静的結合です。 c が core オプジェクトであることは実行時でも変わらないから です。そのため、 grade が仮想関数であっても、コンパイラはこの関数呼び出しをコンパイル時に決定してしま うのです。しかし、 3 、 4 番目の関数呼び出しでは、 p や r の指すオプジェクトの型が実行時までわかりません。 それらは c 。 re かもしれないし Grad かもしれないのです。そのため、実際にどのバージョンの grad を使うか は、実行時まで決められないのです。実行時の判断は、 p や r の指すオプジェクトの型によってなされます。 基底クラスのポインタや参照が派生クラスのオプジェクトを指すことができるわけですが、これは多態性 ( ポ リモルフィズム ) という OOP の重要な概念の例になっています。この単語はギリシャ語の冗 oA ? 加叩の。、から 来たものですが、「たくさんの形」を意味し、 19 世紀の中ごろには英語の単語になっています。プログラミング の世界では、 1 つの型がたくさんの型を表せることを意味します。 C+ + は、仮想関数の動的結合機能を通して、 多態性をサポートしているのです。ポインタや参照を通して仮想関数を呼び出すことを、多態性呼び出しという こともできます。参照 ( やポインタ ) そのものの型は決まっていますが、それが指すオプジェクトの型は、参照 ( やポインタ ) が最初に意図した型でも、その派生クラスの型でもよいのです。これにより、 1 つの型を通して、 たくさんの関数を呼び出せるわけです。 仮想関数について最後にコメントがあります。この関数は、実際にプログラムで使うかどうかに関わらず定義 が必要であるということです。仮想でない関数は、プログラム中で使わないなら、宣言だけで定義をしないとい うことも可能でした。しかし、仮想関数については、宣言だけで定義を書かないとコンパイラは不思議なエラー メッセージを出すことでしよう。もし、コンパイラが、理解できないエラーメッセージを出したら、そしてその メッセージが「何かを定義していない」という意味のものなら、すべての仮想関数の定義を書いたかチェックし てみるとよいでしよう。もし書き忘れがあれば、それを直すだけでエラーがなくなるはずです。 13.2.3 まとめると 先に進む前に こまでをまとめておきます。さらに、少しだけ書き換えもします。 read 関数も virtual に しておくのです。 read 関数も、実行時の型によって異なるバージョンが呼び出されるようにしておきたいから です。この書き換えをして、まとめると、クラスは次のようになります。 class Core { public : Core() : midterm(0) , fina1(O) { } Core(std: :istream& (s) { read(is) ; } std: : string name() const ; / / 13 . 1 . 2 で定義したもの virtual std: : istream& read(std: : istream&) ; std: : string n; / / c 。 re のみからアクセスできる private : std: :vector く double> homework; double midterm, final ; std: : istream& read—common(std: : istream&) ; / / 派生クラスからアクセスできる protected: virtual double grade() const ;
14.1 オブジェクトをコピーするハンドル 257 組み込みの演算子 * をポインタに適用すると、そのポインタの指すオプジェクトが得られます。こでは独自の て扱えるものを戻すことになっているのです。 operator-> を定義すると、それを持つオプジェクト x に対して、 要求するような名前に直接アクセスすることはできません。代わりに、 C + + の言語仕様では、一 > はポインタとし へのアクセスに使われます。そしてオペランドに使われる名前は工クスプレッションではないのです。ユーザが ー > は左オペランドのメンバである右オペランド とは違います。スコープ演算子やドット演算子 ( . ) のように →演算子はもう少し複雑です。表面的には→は 2 項演算子のように見えますが、実際には普通の 2 項演算子 *student は student の初期化で生成した Grad オプジェクトへの参照になるわけです。 に * を適用したものになるのです ( ここでは p にアクセスできるとして説明しています ) 。つまり上の例では、 込みの * を適用したものを戻すようにしてあります。 student オプジェクトに対して *student は、 student ・ p * を定義しているわけですが、これを Hande オプジェクトに適用すると、それが保持しているポインタに組み (x ・ operator->() )->y と同じ意味になるのです。今考えているコードでは、 student に対して、 student—>y (student . operator->() )->y student . p->y と同じ意味になり、これは。 perat 。 r ー > の定義により、 operator-> は、 内部のポインタを戻します。そのため、 と同じ意味になるのです (student . p には直接アクセスできませんが、そのような意味になるということです ) 。 どのバージョンの grade が呼び出されるか実行時に決められることになります。 照を戻すので、 (*student) ・ grade() を実行すると、 grade は参照を通して呼び出されることになり、これも、 Grad オプジェクトを指しているなら、これは Grad : : grade の呼び出しになります。同様に、 operator* は参 び出されるかは、 p が指している実際のオプジェクトの型によって決まるのです。 student が上の例のように れは student 内部の p に対して grade を呼び出すことになります。この場合、どのバージョンの grade が呼 インタを戻すので、それにより動的結合が成立するのです。たとえば、 student->grade() を実行すると、 operator* と operator ー > の定義を見ると、すでにそのようになっていることがわかります。これらは参照かポ 組み込みのポインタに対して働く多態性の仕組みが Hand1e にも受け継がれることが 1 つの目標でした。 に渡すことになるのです。 そこで一 > 演算子を使うと、 Hand1e オプジェクトに対する関数呼び出しの要求を、その Hand1e が持っポインタ
14.4 コピーがコントロール可能なハンドルの改良 そして、 make-unique メンバの定義を変えます。 template く class T> void ptr く T> : :make—unique() if (*refptr ! = 1 ) { —*refptr; refptr = new size—t(l) ; p = p? clone(p) : 0 ; / / メンバではなくグロ - バルな cl 。 ne の呼び出し 267 このような関数を定義し、 make ー u Ⅱ ique の定義に挟み込んでも、その動作が変わらないことはあきらかだと思 います。グローバルな clone の呼び出しは、コピーされるオプジェクトのメンバ関数である clone の呼び出 しになるからです。 make-unique は、メンバでない clone を呼び出し、その clone が P の指すオプジェクト についてそのメンバ関数を呼び出す、というように、間接度の度合いを上げたのです。 student-info のよう に C10 Ⅱ e をメンバに持っているクラスについては、これは何の得にもなりません。しかし、 str で見たように clone をメンバに持たないオプジェクトを ptr が指すような場合には、これが問題解決につながるのです。 こで次のような関数を定義します。 / / ptr く vec く char> > を有効にするための関数 template く > Vec く char>* c10ne (const Vec く char>* vp) return new Vec く char>(*vp) ; template く > を最初に書くことで、この関数はテンプレート特化 (template specialization) であることを示しま す。このようにすることで、特定の引数型に対して特定バージョンのテンプレート関数を定義できるのです。 れで、 vec く char> のポインタを引数にしたときは、他のポインタを引数にしたときと異なる動作をするように定 義できたことになります。 clone に vec く char > * 型引数を渡すと、コンパイラは上記の特別バージョンの cl 。Ⅱ e を呼び出すのです。他のポインタを引数に渡すと、コンパイラはテンプレートの clone から、「引数のポインタ に対して clone メンバ関数を呼び出す」関数を具体化します。特化したバージョンでは、引数から Vec く char> のコピーコンストラクタを使って、新しい vec く char> オプジェクトを生成します。このような clone の特化で は仮想関数の仕組みが使えませんが、 vec の派生関数は考えないのでこれは必要ありません。 こでしたことは、メンバ関数 clone への依存を弱めたということです。それはこの関数がメンバ関数とし て存在しない可能性もあるからです。間接度を上げることで、特定のクラスのコピーの仕方を clone の定義で 指示できるようにしました。メンバ関数、コピーコンストラクタ、あるいはまったく違う方法を使うように決め られるのです。テンプレート特化がなければ、 ptr クラスはデータオプジェクトの clone メンバ関数を使いま ・ ptr く t>:make-unique を使い、しかも T: :clone が定義されているなら、 make-unique は T: :clone を ない。 ・ Ptr く T > は使っても ptr く T>::make-unique を使わないなら、 T::c10ne は定義されていなくてもかまわ しかし、それは make ー u Ⅱ ique が使われるときだけです。これは次のようにまとめることもできます。 す。
266 第 14 章メモリを ( ほとんど ) 自動で管理する str のインタフェースは変えていませんが、実装は根本的に変えました。 vec オプジェクトを直接保持する代 わりに、 vec オプジェクトを指す ptr オプジェクトを保持するようにしたのです。これより、複数の str オプ ジェクトが同じデータオプジェクトを共有できるようになりました。コンストラクタは vec オプジェクトを適 当に初期化して生成し、 ptr オプジェクトをこれに結び付けます。読み込み専用でデータを変えない関数の定義 は前と同じです。もちろん、これらの関数は ptr に対して実行されるので、 ptr 内のポインタを通じて文字デー タを取り出さなければなりません。おもしろいのは、入力演算子、結合演算子、 const でない口演算子といっ た、 Str を変更する演算子関数です。 たとえば、 str : : operator + = を見てください。これはすでにある vec オプジェクトにデータを追加する演算 子なので、 data ・ make-unique() を呼び出しているのです。この関数呼び出しの後なら、 str オプジェクトの ptr は、自由に変更してよいオプジェクトのコピーを指すようになるわけです。 14.4 ュコードを変更できない型のコピー しかし残念ながら、 make-unique の定義には重大な問題があるのです。 template く class T> void Ptr く T> : : make_unique ( ) if (*refptr ! = —*refptr; refptr = new size—t ( 1 ) ; P = P? p->clone() : 0 ; / / に門題あり p ー > clone の呼び出しを見てください。今は ptr く Vec く char> > を使っているので、これは Vec く char> のメン バ関数 cl 。 ne を呼び出すことになります。しかし、 vec にそんなメンバ関数はないのです。 こまでの仕掛けでは、 clone は ptr が指すオプジェクトのメンバ関数でなけばなりません。そうすること で仮想関数の仕掛けが使えるからです。言い換えれば、 ptr がすべての派生クラスに対してきちんと働くために は、 clone という関数がそのクラスに定義されていることが非常に重要なのです。しかし、また、 vec の定義を 変えることはできません。 vec は vect 。 r の機能を部分的に実現するクラスとして書いたものです。 vector に は cl 。Ⅱ e などという関数はありません。これを vec に付け加えると、 vect 。 r の機能の一部を実現するという 前提も壊れてしまいます。どうすればよいのでしよう。 このような困った問題への解答は、しばしば、冗談めかして私たちがいう、ソフトウェア工ンジニアリング の基本定理にあります。それは、すべての問題は間接度の度合いを上げれば解決できる、というものです。問題 は、存在しないメンバ関数を呼び出そうとしていること、そしてそのメンバ関数を新たに書き加えることができ ないということでした。解答は、直接メンバ関数を呼び出すのではなく、オプジェクト生成のために呼び出せる グローバル関数を定義し、それを間に使うというものです。この関数の名前も clone とします。 template く class T> T* clone(const T* tp) return tp—>clone() ;
172 このとき、 fp が next を指すようにできるのですが、それには、次のような 2 通りの書き方があるのです。 こで fp と型の合う 呼び出しでなければ、たとえ & を使わなくても、アドレスの代入と考えることができます。 これを使ってできることは、関数のアドレスを代入することか、それを呼び出すことだけです。そこで、関数 fp が i Ⅱ t 型の引数を取り int 型の戻り値を持っ関数へのポインタになるのです。 と書いて、 *fp が int 型の引数を取り int 型の値を戻す関数であることを示すことになります。これにより、 int (*fp) (int) ; で、 *P が int であることを示し、結果として p がポインタであると宣言するように int *p; 関数へのポインタの宣言は他のポインタの宣言と同様です。たとえば、 タの値を他のポインタに渡すかだけなのです。 同様の振る舞いをします。ただ、関数へのポインタでできることは、もとの関数を呼び出すことか、このポイン 関数そのものではなく、関数へのポインタを使ったということなのです。関数へのポインタは、他のポインタと にもかかわらす、 6.2.2 の write ー analysis 関数の引数に、他の関数を使いました。これは、コンパイラが のアドレスを使うことです。 はできません。それができるのはコンパイラだけです。プログラム中でできることは関数を呼び出すことか、そ 第 10 章メモリ管理と低レベルのデータ構造 リメン 1 1 最後に 次のような関数があったとします。 int next (int れ ) return 十 1 ; / / 以下の 2 つの書き方は同し意味 fp = &next ; fp = next ; 同様に、 i という名前の int 型変数があるとすると、 next を呼び出すのに fp を次のように使って i をインク / / 以下の 2 つの書き方は同し意味 トできるのです。 もし他の関数を引数に取るような工クスプレッションを書けば、コンパイラはそれを関数そのものでは なく関数へのポインタとして扱うのです。そのため、たとえば 6.2.2 の write ー analysis 関数では、パラメー タを double analysis (const vector く Student-info>&) と書くことができたのです。これは、 double (*analysis) (const vector く Student—inf0>&)
4.2 データの構成 63 この関数は stude Ⅱ t ー inf 。オプジェクトを引数に取り、 d 。 uble である最終成績を戻します。パラメータの型が student-info ではなく const student-info& なのは、この関数を呼び出すときに引数の student-info オ プジェクトのコピーで余分な時間を使わないためです。 この関数では内部で呼び出している grade の例外処理を乗っ取るようなことはしていません。なぜなら、今 の場合、内部で呼び出している grade の例外処理以上のことはできないからです。今定義した grade は例外を catch しないので、投げられた例外はこの関数の呼び出し側に届くはすです。この関数の呼び出し側は、宿題を していない学生をどう扱うべきか知っているはずでしよう。 プログラム全体を書く前に残った仕事は、 student_ inf 0 をどのようにソートするか決めることです。 median 関数 ( 4.1.1 ) では、 vec という名前の vector く double > パラメータを sort という関数でソートしました。 sort (students . begin() , students . end() ) ; / / 完全には正しくない とはできません。 しかし、 vector く student-info> のオプジェクトを students とした場合、単純に以下のようにソートするこ sort(vec. begin() , vec . end()) ; ドのほうが右のオペランドより小さいと考えられるのです。これはまさに望ましいものです。 序に従います。つまり、アルファベット順で左のオペランドが右のオペランドより前にあるなら、左のオペラン 較の演算子くが定義されているので可能なのです。 string オプジェクト同士のくによる比較は普通の辞書式の順 この関数は単純に student-info 同士の比較を string 同士の比較にしているだけです。これは string に比 return X . name く y . name ; b001 compare(const Student—info& x, const Student—inf0& y) name のみを比較することにします。 で戻す関数を定義すればよいのです。今は、学生を名前のアルファベット順にソートしたいので、比較の関数は student-inf 。オプジェクトを引数に取り、前のオプジェクトが後のオプジェクトより小さいかどうかを真偽値 れると、 sort 関数はくの代わりに この関数を要素の比較に使うことになっています。したがって、 2 つの す。この判定関数というものは、真偽の値、特に bool 値を戻す関数のことです。この 3 番目の引数が与えら 幸い、 sort 関数は 3 番目の引数に判定関数 ( 述語関数、 predicate) というものを持たせることもできま 比較しようとすると、コンパイラがエラーメッセージを出します。 子は、 student ー inf 。オプジェクトには意味を持たないのです。実際、 sort を使いこれらのオプジェクトをくで れでは、 sort が 2 つの student-inf 0 オプジェクトを比較しようとした場合、何がおこるでしようか。く演算 vector く double> をソートできるのは、 double のデータ同士にはくが使え、正しい結果が得られるからです。そ 比較は、ソートをしたい vector 内のデータの型に対して、く演算子を使うことで行われます。たとえば、 この sort 関数が vector の中のデータを並べ替えるのに、データ同士を比べる必要があります。この タに順番を付けるのでしよう。 sort はどのように vector 内のデー 少しだけ、 sort がどのように働くか考えてみるのもよいでしよう。特に どうしてでしよう。 sort や他のライプラリのアルゴリズムについて、詳しくは第 6 章で説明しますが、
13.4 簡単なハンドルクラス 245 指す場合、 student-info: :n e の呼び出しは core: :name の呼び出しになります。しかし、 cp が 0 なら name は例外を投げますが、これは compare を呼び出しているコードまで到達します。 compare が student-info のパプリックなインタフェースを使っているので、 cp を直接チェックする必要はないのです。ユーザレベルの コードと同様に、チェックは student ー i 0 クラスに任せているのです。 13.4.1 ハンドルを読む read 関数には 3 つの責任があります。ます、このハンドルに結びついているオプジェクトがすでにあれば、 これを破棄する必要があります。それから、読み込むべき学生の種類を決めなければなりません。そして、正し いオプジェクトを生成し、 istream から読み込んだデータでこのオプジェクトを初期化しなければなりません。 istream& Student—info : :read(istream& is) / / もしあれは。前のオプジェクトを破棄 delete cp; char ch ; / / 学生データのタイプを読み込む iS > > ch; if (ch cp = new Core(is); } else { cp = new Grad(is); return iS ; read 関数は、 ( もしあれば ) このハンドルに結び付けられた前のオプジェクトを破棄するところからはじめます。 、 cp が 0 かどうかをチェックする必要はありません。 C ト + では、 0 という特別な値を持った delete を使う前に ポインタに delete を適用しても問題がないようになっているからです。前のオプジェクトを破棄すれば、新し いデータを読み込むことができます。まず、行頭の 1 文字を読み込み、チェックします。そして、この文字にし たがって、正しいオプジェクトを生成するのです。この際、 istream を引数に取る適当なコンストラクタを使っ てデータを初期化します。これらのコンストラクタは、自分のクラスの read 関数を呼び出し、入力ストリーム から新しいオプジェクトにデータを読み込むのです。オプジェクトが生成された後は、そのアドレスを cp に格 納します。そして最後に、読み込み用に与えられた入力ストリームを戻します。 13.4.2 ハンドルオブジェクトのコピー core のポインタを扱うために、コピーコンストラクタと代入演算子が必要です。コンストラクタは、オプ ジェクトを生成しそのアドレスをこのポインタに渡し、副作用として read を呼びます。 student-info のコ ピーを作るときには、新しいオプジェクトを生成し、コピー元のオプジェクトのデータをそのオプジェクトに与 えます。しかし、思わぬ問題があります。それは、どの型のオプジェクトをコピーしているかです。コピーしよ うとしている student-inf 0 が保持しているオプジェクトが core オプジェクトか Grad のような core の派生 クラスのオプジェクトかを判断する方法はないのです。 この問題を解決するには、 core とその派生クラスに新しい仮想関数 cl 。 ne を付け加えます。この関数は新し いオプジェクトを生成し、オリジナルの値をそのオプジェクトにコピーする関数です。
72 第 4 章プログラムとデータの構成 他の定義と同様に、構造体の定義は 1 つのソースファイルに重複して書くことはできない。そのため、通常、重 複インクルードを避けるようにヘッダを書く必要がある。 関数 : 関数を使うファイル内には、その関数の宣言が必要。また、定義は重複してはならない。宣言と定義は 同じ形である。 e / れ〃 c 〃 07 ト〃 ame ( param-dcls ) ; / inline ノ 7 ℃イ e 和〃 c 07 トれ ame (parm-decls) { / / 関数の定義はここに書く こで戻り値の型 7 ℃ e は関数が戻す値の型を意味し、パラメータの宣言四 ram 記 c なはコンマで区切られた 関数のパラメータの型のリストである。関数は先に宣言されていなければ、呼び出すことはできない。引数の型 は対応するパラメータの型と同じかそれに変換されるものでなければならない。戻り値の型がより複雑な場合、 関数の宣言や定義の形は違ってくる。これについては A. 1.2 に書いたので参照のこと。 関数名はオーバーロードできる。つまり、同じ名前でも異なる個数や型のパラメータを持つ関数を定義でき る。コンパイラは同じ型の const 付きの参照と const なしの参照を異なる型として区別する。 オプションとして関数の定義に inli Ⅱ e を付けることができる。これは関数呼び出しのオーバーヘッドを避け るため、可能な場合は、関数を呼び出している場所に関数の定義コードを展開するという意味である。ただし、 必要なら展開されるコードは定義コードを少し変形したものにもなる。これをするためにはコンパイラは関数の 定義を知っている必要がある。そのため inline 関数の定義はソースファイルではなくへッダファイルに書き 込む。 例外処理 : try { / / コード例外を投げる ( スローする ) かもしれないプロックの始めに書く。 } catch(t) { / * コード * / } try のプロックを終了し型 t に一致する例外の処理をする。 cat ch の後の中力ッコの何に は型 t の例外を処理するためのコードを書く。 現在の関数を終了し、例外の値 e を呼び出し側に投げる。 例外クラス : ライプラリはいくつかの例外のクラス ( 型 ) を定義している。それぞれがどのような例外を報告 するものかは名前から推測することができる。 logic—error domain—error invalid-argument length-error out-of -range runt ime—error overflow—error underflow-error range—error 工ラーを起こした原因についての報告を戻す e . what() ライブラリにある仕組み : 文字列 sl と s2 を辞書的順序で比較する。 s 1 く s 2 義 定 の の 数 数 関 関 throw e ;