使う - みる会図書館


検索対象: Accelerated C++ : 効率的なプログラミングのための新しい定跡
314件見つかりました。

1. Accelerated C++ : 効率的なプログラミングのための新しい定跡

270 第 15 章文字絵をもう 1 度 デザインの問題はもっと難しいものです。私たちが生成するそれぞれの絵には構造があり、それを記録してお きたいのです。絵を生成するには、単なる文字の集まりをもとにすることもあるし、また以下の 3 つの関数を使 うこともあります。 3 つの関数とは、絵にフレーム ( 枠 ) を付ける frame 、水平方向に絵をつなげる hcat 、垂直 構造のモデル化に継承を使う ります。そしてその違いを記録しておきたいのです。 別の言い方をすれば、 4 種類の絵があるということです。これらの絵は共通点もありますが、生成方法は異な 方向に絵をつなげる vcat です。 15.1 ユ ました。その名前を p i c-base とすると、継承関係は下図のようになります。 いるか知らすに、ユーザは上記の関数を使えるわけです。これらのクラスは共通の基底クラスから派生するとし 処理している絵の型を正確にしらなくても、必要な処理ができるのです。こうして、どの種類の絵が操作されて 承によってこれらのクラスを関係付けることで、仮想関数を使うことができます。仮想関数を使ったコードは、 します。同様に、 2 つの絵を水平方向と垂直方向につなげて生成する絵は HCat ー Pic 、 VCat_Pic とします。継 ユーザが与えた文字列だけから生成する絵のクラスは、基底クラスから派生させて stri Ⅱ g ー Pic という名前に 定義するのです。 取り出してそれを共通の基底クラスにします。そして必要な個々のクラスはこの基底クラスの派生クラスとして したがって、これらのデータ構造を継承で表現するのは、賢明なことです。そこで、すべての絵に共通の部分を それらの違いが重要になることもある」という状況なのです。今の場合、すべてのデータ構造は絵を表します。 私たちの問題の解決には、継承がびったりです。「似ているけれど異なる種類のデータ構造がある。そして、 Pic_base String—Pic Frame_Pic HCat_Pic VCat_Pic 次に考えるべきことは、継承関係をユーザに見えるようにするかどうかということです。そうしなければならな い理由はあまりなさそうです。考えている関数は、特定の種類の絵を特別扱いはしません。逆に、抽象的な意 味で ( 全部の種類の ) 「絵」を処理するものなのです。したがって、継承関係を表に出す必要はないのです。さ らに、参照カウンタの方法を使いたいと考えているので、継承と付随する参照カウンタを見えなくしたほうが、 ューザにとっても便利になるのです。 ューザが Pic-base やその派生クラスを直接扱うようにするのではなく、「絵」を表すインタフェースクラス を作ります。ユーザにはそのクラスにアクセスさせ、実装の詳細は隠すのです。特に、インタフェースクラス を作ることで、継承関係や ptr を使っているという事実を隠蔽できます。これで、あきらかに 6 つのクラスを 定義する必要が出てきました。インタフェースのクラス、基底クラス、 4 つの派生クラスです。そして、インタ フェースのクラスを Picture とよぶことにします。内部的には Picture は ptr を使ってデータを管理するこ とにします。 さて、それはどういう ptr でしよう。つまり、 ptr はどの型のオプジェクトを管理することにするのでしょ う。それは実装のクラス Pic_base を管理するクラスです。つまり、 Picture クラスは 1 つのデータメンバを

2. Accelerated C++ : 効率的なプログラミングのための新しい定跡

110 6.2 成績処理の仕組みを比べる 第 6 章 ライブラリのアルゴリズムを使う 4.2 では学生の最終成績を宿題の点数のメジアンなどを使って算出することを考えました。悪賢い学生はこ の仕組みを悪用して、宿題全部を真剣にしないかもしれません。結局、全宿題中下半分の点数は、最終成績に何 の影響もないのです。ある数の宿題をうまくこなしたなら、もうあとは提出しないかもしれません。 私たちの経験では、大抵の学生はこのような抜け道を使いません。しかし、あるところでは、喜んでしかも隠 しもせずにこのようなことをする学生もいました。そこで、宿題をあまりしなかった学生の最終成績は、宿題を すべてした学生のものより悪いだろうかと思いました。この問題を考える一方で、成績処理の仕組みを変えたら どうなるかを調べてみたくなりました。これは、次の 2 つの仕組みのどちらかを採用するということです。 ・メジアンではなく平均を使う。提出しなかった宿題は 0 点として計算する。 ・学生が提出した宿題のみのメジアンを考える。 これらのすべての仕組みでの成績処理の結果と、最初のメジアンを使う成績処理の結果を、宿題を全部提出した 学生とそうでなかった学生について比べてみたかったのです。 こで 2 つの異なる問題を解決するプログラム が必要になったわけです。その問題とは次のものです。 1. 全学生の成績データを読み込み、すべての宿題をした学生とそうでない学生を分ける。 2. それぞれのグループについて、それぞれの成績処理の仕組みを使って最終成績を出す。 また、始めに考えた全宿題のメジアンを使う方法も実行する。 6.2 ユ学生の成績データを扱う 最初の問題は学生の成績データを読み込み、分類することです。幸い、この問題を途中まで解決してくれる コードは書きました。 4.2.1 の student_info と 4.2.2 の read 関数です。まだ足りないところは、学生が宿 題全部を提出したかどうかのチェックです。そのための関数を書くことは簡単です。 b001 did—all-hw(const Student-info& s) return ( (find(s. homework. begin() , s . homework. end() , 0 ) ) s . homework. end ( ) ) ; この関数は s. homework を調べ、 0 が記録されているかどうかをチェックしています。私たちは、提出さ えすれば何がしかの点を与えていたので、 0 点というのは未提出という意味だからです。 find の戻り値と homework. end() を比較していますが、 find は探しているものが見つからなかった場合、 2 番目の引数を戻す ことになっているからです。 read とこの関数を使えば、学生の成績データを読み込み、分類するコードはすぐ書けます。学生のデータを 1 つずつ読み込み、宿題を全部提出したかをチェックし、 did ( 「した」という意味 ) と didnt ( 「しなかった」と いう意味 ) の 2 つの vector に振り分けていくのです。この vector にはもっとよい名前が思いつかなかったの でこのようにしました。一方、意味のある処理をするために、どちらの vector も空でないことを確かめること

3. Accelerated C++ : 効率的なプログラミングのための新しい定跡

B. 3 アルゴリズム binary—search(), e, t) ( ソートされた ) シーケンスで前方向反復子 b と e の間のシーケンスを探索し、値 t があるかどう かを bool 値で戻す。 copy(), e, d) e , t ) e , t ) e , p) 321 入力反復子 b と e の間のシーケンスの値を、出力反復子 d で示される位置にコピ 先には、あらかじめ、コピーする値を保持するのに十分なメモリがあると仮定する。コピー先の最 後の要素の 1 っ後を指す反復子が戻される。 e, b2) e , b2 , p) ーする。 コピー equal (b , equal(b, fill(b, find(b, find—if(b, 入力反復子 b と e の間のシーケンスの要素が、同じサイズで入力反復子 b2 から始まるシーケンス ない場合は e を戻す。 戻す。下の形では、判定関数 p が最初に true になる要素を指す反復子を戻す。そのような要素が 上の形では、入力反復子 b と e の間のシーケンスの中で、 t と最初に一致する要素を指す反復子を e , p) 入力反復子 b と e の間のシーケンスの要素の値を t に設定する。 vo id を戻す。 演算子を使って行われ、下の形では判定関数 p を使って行われる。 の要素と等しいかどうかを b 。 01 値で戻す。 2 つの要素が等しいかどうかの判定は、上の形では = lexicographical-compare(), e, b2, e2) lexicographical—compare(), e, b2, e2, p) るなら、短い方が「小さい」。そうでない場合は、それぞれを 1 要素すっ比較していき、最初に異 れる。 2 つのシーケンスの比較は、次のように行う。ます、どちらかがもう一方の前の方と一致す b 。。 1 値で戻す。比較は、上の形ではく演算子を使って行われ、下の形では判定関数 p を使って行わ 範囲 [b , e) のシーケンスの要素が範囲 Cb2 , (2) のシーケンスの要素より小さいかどうかを なる要素が出たら、その「大小」がシーケンスの「大小」になる。 b 、 e 、 b2 、 e2 は入力反復子であ max(tl, min(tl, ればよい。 t2) t2) t 1 、 t2 のうち、 max の場合は大きい方を、 min の場合は小さいほうを戻す。 t 1 と t2 は同じ型の 値でなければならない。 max—element (b , e) min-element (b , e) 前方向反復子の b と e の間のシーケンスの要素で、 1 番大きい ( 1 番小さい ) 要素を指す反復子を stable—partition(b, partition(), e, p) 戻す。

4. Accelerated C++ : 効率的なプログラミングのための新しい定跡

11.3 コヒ。ー管理 197 この関数には新しい考え方が 2 、 3 使われていますので、説明しましよう。 まず、クラス定義の外でテンプレート関数を定義する形式です。他のテンプレートのときと同様に、コンパイ ラにこれがテンプレートであることを知らせ、型パラメータを書く必要があります。それから戻り値ですが、 れは Vec<T>& になっています。これはクラス定義 ( ヘッダファイル ) 内の宣言では vec& になっています。そこ では戻り値にわざわざ型パラメータを付けなかったのです。実は、入力の手間を省くために、テンプレートの内 部では型パラメータを書かなくてもよいようになっています。そのため、クラス定義の中は、テンプレートの中 なので、く T> を繰り返す必要がないのです。しかし、クラス定義の外で戻り値を書くときには、テンプレートの スコープ外なので、 ( あれば ) 型パラメータを明示的に書かなければならないのです。同様に、関数名も単純に vec: :operator ではなく、 vec く T>: :operator= です。しかし、 1 度、これが vec く T > のメンバであると特定し た後なら、その後ではく T > を繰り返す必要はなくなります。そのため、引数の型は c 。 nst vec & とできるのです。 これは const vec く T>& と書くこともできるのですが。 この関数のもう 1 つの新しいところは、 this というキーワードを使っていることです。 this はメンバ関数の 中だけで有効なキーワードで、メンバ関数が動作しているオプジェクトへのポインタになっているのです。た とえば、 Vec::operator= の中では、メンバ関数 operator = が動作する vec オプジェクトへのポインタなので、 this は vec* 型です。代入演算子では、 this は左辺のオペランドを表します。一般に、 this はオプジェクト自 身を表したいときに使われるのです。今の例では、 if の条件と return のところでそのように使われています。 代入の右辺と左辺のオプジェクトが同じものかどうか判定するのに this を使っています。もし、同じものな ら同じアドレスを持つはずです。 10.1.1 で見たように、 &rhs は rhs のアドレスになります。これと this を くらべて自己代入かどうかを直接チェックしているのです。右辺左辺が同じオプジェクトなら、代入演算子のす ることは何もありません。そこですぐに return してしまうのです。一方、異なるオプジェクトであった場合、 左辺のオプジェクトのメモリを解放し、そこに新しく配列を生成し、右辺のオプジェクトのデータを代入しま す。あきらかにもう 1 つのユーティリティ関数で vec の要素を破棄しそのメモリを解放するものが必要になり ます。これを uncreate とします。この関数 uncreate を呼ぶと古い値が破棄されるので、次に左辺のメモリの 確保と右辺から左辺へのデータのコピーに create 関数が使えるのです。 こでは、左辺と右辺を直接比較して同じ 代入演算子が自己代入をきちんと処理することは非常に重要です。 このチェックを代入演算子のコードか かどうかをチェックすることで処理しました。この重要性を見るために ら取り除いたらどうなるかを考えてみましよう。この場合、常に左辺のオプジェクトに対し uncreate を実行 こで、左辺と右辺のオプジェクトが同じものなら、右辺の し、データを破棄しメモリを解放してしまいます。 オプジェクトも同じメモリ空間を指すポインタをデータとして持っているわけです。そして、左辺のオプジェク トのデータを生成するために create を使えば、ひどいことになるでしよう。左辺のメモリを解放すると右辺の メモリも解放してしまうからです。 create が rhs のデータをコピーしようとしたとき、そのデータは破棄され メモリは解放されているからです。 こで行ったような直接的なチェックは、自己代入を扱うためのよくある方法ですが、これ以外にも方法はあ りますし、常にベストの方法でもありません。重要なことは、自己代入を正しく扱うということです。それを実 際にどうするかは方法の問題に過ぎません。 return ステートメントに関してはもう 1 つ面白いことがあります。これは this の指すオプジェクトを取り 出し、それを戻しています。参照を戻す場合に重要なことは、その関数が終了したあとも参照しているオプジェ クトは残るものでなければならないということです。ローカルな変数の参照を戻すとひどいことになります。そ れは関数が終了した後では、その参照するオプジェクトは破棄されてしまい、参照はゴミを指すことになるから

5. Accelerated C++ : 効率的なプログラミングのための新しい定跡

4.3 みんなまとめて 67 一般に、ヘッダファイルは必要な名前のみを宣言すべきです。必要な名前に限定しておくことで、ユーザの自 由度を最大にしておけるからです。たとえば、 median 関数のユーザが vect 。 r をどう表現するかわからないの で、修飾っきの std::vector にしたのです。 median 関数のあるユーザは vector に usi Ⅱ g 宣言を使いたくな いかもしれません。しかし、ヘッダファイルで using std : : vector を宣言していると、このファイルをインク ルードしたプログラムでは、使いたくなくてもこの宣言が使われてしまうのです。要するに、ヘッダファイルは using 宣言ではなく、修飾された名前を使うべきなのです。 あと 1 ついうべきことがあります。それはヘッダファイルは 1 つのプログラムで 2 度以上インクルードされ ても問題が無いようにしておかなければならないということです。実は今の medi an . h は、宣言しか含んでいな いので、 2 度以上インクルードされても問題ありません。しかし、必要のない場合でも、複数回のインクルード に対処する仕組みを入れておくことをお勧めします。これはプリプロセッサというものをファイルに付け加える ことでできます。 #ifndef GUARD_median_h #def ine GUARD_median_h / / median. h : 最終バージョン #include く vector> double median(std: :vector く double>) ; #endif 1 行目にある #ifndefi•ィレクティブ (directive) はプリプロセッサ変数 (preprocessor variable) 、 3 とよばれる ものが定義されているかどうかをチェックするものです。今の場合、それは GUARD-median-h で、これが定義 されているかどうかをチェックしているのです。このプリプロセッサ変数はプログラムのコンパイルの仕方を制 御するのに使われる変数です。しかし、プリプロセッサの詳細は本書の範囲を越えてしまうのでここでは説明し ません。 今の場合、 #ifndef ディレクテイプは、もし与えられた名前 ( プリプロセッサ変数の名前 ) が存在しなければ 次に現われる #endif までの間のものを処理するように、プリプロセッサに要求しているのです。この名前はプ ログラム全体の中で同じものがないようにします。そのため、 GUARD- のような他では使われそうにない文字列 を一部に使ってこの名前を定義しているのです。 最初の median. h がインクルードされるときには、 GUARD-median-h は未定義なので、プリプロセッサはこの ファイルの中身をすべて処理します。そして最初にすることは、 GUARD-median-h の定義です。そのため、 2 回 目以降にこのファイルがインクルードされるときには、 GUARD-median-h が定義されているので、処理は何もさ れないのです。 最後に付け加えることは、 #ifndef はコメントよりも前、ファイルの 1 行目にするとよいということです。 #ifndef セ ar 0 と乙 e #endif それは C + + のコンパイラは、この構造のファイルを見つけ、そのプリプロセッサ変数勧 / e が定義されている ことがわかると、もう読み込みをしないからです。 * 3 訳注 : す。 こでは GUARD-median-h という名前の変数を定義して使っていますが、 この名前はプログラマが自由に決められるもので

6. Accelerated C++ : 効率的なプログラミングのための新しい定跡

155 第 9 章 新しい型を定義する C + + には 2 つの型があります。 1 つは組み込み型でありもう 1 つはクラスです。組み込み型とは 新の一部 、に 1 ロロ として定義されている型であり、 char 、 int 、 double などがそうです。一方、 string 、 vector 、 istream な どライプラリで定義されている型はみなクラスです。入出力ライプラリにある、いくつかの低レベルでシステム 依存のルーチンを除くと、ライプラリのクラスも一般のプログラマが自分のアプリケーションのために使える仕 組みを使って書かれているものです。 組み込み型と同様に簡単に使える型をプログラマが自分で作り出せるということが、 C ト + という言語のデザイ ンにおいては特に重視されています。後で見ますが、ストレートで直感的なインタフェースを持ったクラスを作 二五にそのような機能がないとできません。 るためには、そのための感性や判断も必要ですが こでは第 4 章 の成績処理問題を考えながら、クラスを定義するもっとも基本的な仕組みを紹介していきます。第 11 章からは、 これらの基礎の上に、ライプラリのクラスのように完璧なクラスをどのように作るかを見ていきます。 9.1 Student-info をもう 1 度 4.2.1 では、 student-info という単純なデータ構造といくつかの関数を書きました。これは学生の成績処 理プログラムに使うためのものです。しかし、そこで作ったデータ構造と関数は必ずしも他のプログラマにも使 い易いものではありません。 私たちが意識するしないに関わらず、私たちの関数を使いたいと考えるプログラマは、いくつかの規則に従う 必要があります。たとえば、 student ー inf 0 オプジェクトのユーザは、ますそのデータを読み込んでそのオプ ジェクトに与えるようにしました。これをしないければ、 vector である homework が空のままになり、 midterm と final は不定な値を持ってしまいます。これらの値をそのまま使うと結果は予想できないものになります。 つまり、単純に結果が間違っているか、悪いときにはコンピュータがクラッシュしてしまいます。さらに、ユー ザが student-info オプジェクトが有効な値を保持しているかどうかチェックしたい場合は、実際のデータメ ンバを調べるしかありません。これをするには、 student ー inf 。内部の詳細な知識が必要になります。 関連する間題として、私たちのプログラムのユーザは「学生のデータをファイルから読み込んだらそのデータ はその後は変更されない」と思いこむかもしれないということが考えられます。しかし、私たちのコードはその ような保証はしていません。 3 番目の問題は、もとの student-info の「インタフェース」がばらばらになっているということです。たと えば、 read のような student-inf 。の状態を変える関数を、 1 つのヘッダファイルにまとめることもできます。 もしそうするならば、私たちのコードのユーザには便利でしよう。しかし、そのようなグループ作りをしないで

7. Accelerated C++ : 効率的なプログラミングのための新しい定跡

14.3 データを共有するかどうかを決められるハンドル 263 このバージョンの Ref _handle は、コピーしたオプジェクト同士が同じデータオプジェクトを持てるような場 合にはうまく働きます。しかし、値のような振る舞いが望まれる student-info ではどうでしようか。たとえ ば、 student-info の実装に Ref-hand1e を使い、次のようなコードを書くとどうなるでしよう。 student-info sl(cin) ; / / sl を標準人力から得たデ - タで生成 = sl; / / その値を s2 に「コピー」 Student_info s2 こうすると、 s2 が sl のコピーに見えても、 sl と s2 が同じデータオプジェクトを指すことになります。どちら かのデータを書き換えると、もう片方のデータも変わってしまうのです。 14.1.1 で定義した元の Ha Ⅱ dle クラスは、コピーの時には clone で新しいオプジェクトを生成するので、値 のように振る舞います。しかし、あきらかに Ref ー handle クラスは cl 。 ne 関数を使っていません。 clone 関数 を使わないから、このハンドルに結びついているオプジェクトをコピーで生成することはないのです。一方、 Ref-handIe は不要なデータコピーを避けるという長所も持っています。問題は、不要かどうかにかかわらす、 コピーによる生成はすべて避けているということです。どうすればよいのでしよう。 14.3 データを共有するかどうかを決められるハンドル こまでで 2 種類の一般的なハンドルの定義を見てきました。最初のものは、コピーのたびにデータオプジェ クトもコピーするもので、次のものは決してデータオプジェクトはコピーしないというものです。もっと有用な ハンドルは、これを使うプログラムがデータオプジェクトをコピーするかどうか決められるものです。そのよう なハンドルクラスは Ref -handle のパフォーマンスを保ちながら、 Hand1e の振る舞い、つまり値のような振る 舞いをするクラスになります。これは組み込みのポインタの有用さを持ちながら、たくさんの落とし穴を回避す るものとも言えます。そこで、この最終版のハンドルの名前は、ポインタ (pointer) の代わりになるものという 意味で、 ptr とします。 ptr クラスでは、データの内容を変更したいときにそのデータオプジェクトを指してい る別の Ptr オプジェクトがあれば、そのオプジェクトをコピーしてから変更することにします。参照カウンタ があるので、データオプジェクトを指す ptr クラスの数がわかるのです。 Ptr クラスの基本構造は、 14.2 で書いた Ref-hand1e クラスの基本構造と同じです。 ユーザにメモリ管理の指示をさせるメンバ関数を 1 っ増やすことだけです。 template く class T> class Ptr { public : / / 必要な場合、条件によってオプジェクトのコピーをするメンバ関数 void make-unique ( ) { if (*refptr ! = 1 ) { -*refptr ; refptr = new size—t(l) ; p = P? p->clone() : O; / / 残りの部分は名前以外は Ref ー h dle クラスと同し ptr() : refptr(new size-t(l)) , p(0) { } Ptr()* t): refptr(new size-t(l)), p(t) { } こですることは、

8. Accelerated C++ : 効率的なプログラミングのための新しい定跡

40 第 3 章たくさんのデータを扱う 第 2 章で扱った条件には、 bool 値を戻す関係演算子を使いました。また、数値を戻すェクスプレッション も使いました。条件として使われると、数値は b 。。 1 値に変換されるのです。 0 以外の値は t て ue になり、 0 は false になるのです。結局問題になるのは、 cin を条件として使えるようにする変換が istream にあるかどう かということです。 cin の値そのものの説明は今はしませんが、要するにこれが b001 値に変換されるのだとい うことを知っていればよいのです。これにより、 ci Ⅱを条件の中で使うことができるのです。この値は istream オプジェクトである cin の内部状態によって異なります。そして、内部状態には最後の読み込みの結果が残さ れているのです。つまり、 cin を条件に使うということは、最後の読み込みが成功したかどうかをチェックする ・システムが入力装置のハードウェア的な問題を見つけた場合 るものでなく、そのように変換もできない場合 ・ int を入力させるところで整数でないものを入力してしまうというように、入力されるものが期待されてい ・読み込みが入力用のファイルの最後に到達した場合 ストリームからの読み込みが失敗するのは以下のような場合です。 のと同じことなのです。 * 2 訳注 : 複数個のデータの真ん中の値。 ているということです。平均値を計算するならそれでもよいでしよう。しかし、平均値ではなくメジアンがほ こまで作ってきたプログラムにはデザイン上の欠点があります。それは宿題の点数を読み込むとすぐに捨て 3.2 平均ではなくメジアンを使う め、ⅶ ile の終了後に条件の副作用を考える必要はありません。 もし、読み込みに失敗すれば、もうデータを入れられなくなるので、不変な表明はやはり成立します。そのた になるのです。 また成立するようにできます。これらをまとめれば、 while の中の処理全体で不変な表明は、再び成り立つよう やしたため、 sum は count ではなく count ー 1 個の成績の和になってしまうからです。これは sum + = x ; で、 とで、不変な表明の後半部分「 sum はそれらの点数の合計」が成り立たなくなります。なぜなら count を 1 増 なります。したがって、 c 。 unt を 1 増やせば、また不変な表明が成立するようにできます。しかし、こうするこ んだと考えます。そこで、 cin > > x が読み込みに成功したなら、読み込んだ成績データの数は c 。 unt + 1 に 条件を評価する前は、不変な表明が成り立っているとしているので、すでに c 。 unt 個の成績データを読み込 条件の副作用の効果も考えなければならないのです。 み込みに成功すると、不変な表明の前半部分「ここまで c 。 u Ⅱ t 個読み込み、」が正しくなくなります。つまり、 考慮しなくてはなりません。この副作用は不変な表明の成立と不成立に影響を及ばすからです。 cin > > x で読 2.3.2 で見たようなループの「不変な表明」を今の場合に理解するためには、 while の条件の副作用のことを 3.1.2 ループの不変な表明 いては 4.1.3 で説明します。 1 度読み込みに失敗すると、リセットしない限りそのストリームからは読み込みができなくなります。これにつ どのような場合でも結果は同じになり、 cin を条件に使うと、これは false として解釈されるのです。また、

9. Accelerated C++ : 効率的なプログラミングのための新しい定跡

86 第 5 章シーケンシャルコンテナと string の解析 際には新しい要素を付け加えるために全要素を別の場所に移すかもしれないからです。したがって、このような 操作をするループでは、無効になるかもしれない反復子のコピーを利用しないように、とても気をつけなければ ならないのです。特に studetns . end() の値をコピーして使うことは多くのバグのもとになります。 こでは、この性質のため、 list の要素のソート ( 整列 ) のた は、反復子についてさらに詳細な話をします。 こまでに list クラス反復子はランダムアクセスの機能をサポートしないという話をしました。 8.2.1 で は、実際に削除される要素を指す反復子だけです。 一方、 list に関しては、 erase や push-back は他の要素を指す反復子を無効にしません。無効になる反復子 これは list のデータをソートするのに最適なアルゴリズムを使いって めに標準ライプラリの sort 関数を使うことはできないということだけ覚えてください。このため、 list には students . sort (compare) ; 1ist く Student_info> students ; います。そのため、 list のデータをソートするには、次のようにするのです。 sort というメンバ関数があるのです。 これは次のような vect 。 r の場合と違います。 vector く Student_info> students ; sort (students. begin() , students . end() , compare) ; なお、 compare 関数は student-info オプジェクトに作用する関数なので、 もその内部の student ー inf 0 オプジェクトをソートするのに使えるのです。 5.5.2 なぜ、気にするのか ? vector だけでなく list であって 不合格者のデータを取り出す関数は、データ構造の選択が効率にどう影響するかを見るよい例です。このコー ドは要素にシーケンシャル ( 川頁番に ) にアクセスするので、それだけ見れば、 vect 。 r がもっともよい選択です。 しかし、要素をコンテナから削除する部分があるので、これは list がよいということになります。 効率の問題ではいつもそうなのですが、効率をどのように取り扱うかがデータ構造の選択に影響します。効率 の問題は難しいので、一般的にはこの本の範囲外になりますが、データ構造の選択がプログラムの効率を非常に 変えることがある、ということは覚えておくとよいでしよう。入力データ数が少ない場合は、 list を使った方 が vector を使うより遅くなります。逆に、データ数が多いと、 vector を不適当に使ったものは list を使う ものよりかなり遅くなります。データ数の増加に伴って効率がどう変化するかは驚くほどのものがあります。 実際にプログラムの効率を見るため、学生のデータを記録した 3 つのファイルを使って実験してみました。最 初のファイルには 735 個のデータがあり、次のファイルにはその 10 倍のデータが、また最後のファイルにはさ らにその川倍のデータ、つまり 73 , 500 のデータがあるものにしました。次の表は、それぞれのファイルを処理 するのにかかった時間を秒の単位で示したものです。 ファイルサイズ 735 7 , 350 73 , 500 list vector 0.1 0.8 8.8 0.1 6.7 597.1

10. Accelerated C++ : 効率的なプログラミングのための新しい定跡

156 すませてしまうこともあるでしよう。 この章では、 Student_info を拡張して、 9.2 クラス型 第 9 章 新しい型を定義する これらのすべての問題を解決します。 もっとも基本的なレベルでは、クラスとは、関連するデータを 1 つにまとめておく機能ということができま す。これにより、複数のデータを、 1 つのものとして扱えるわけです。たとえば、当4.2.1 では student_info を次のように定義しました。 * 1 struct Student_info { std: : string name ; double midterm, final ; std: :vector く double> homework; これにより、 student-info のオプジェクトを扱うことができました。このオプジェクトは、 4 つのデータを内 部に持つのです。それは、 name という名前の std: :string 、 homework という名前の std: :vector く double> 、 それから midterm と final という名前の double です。 あきらかに この Student—info を使うプログラマは 、ここのデータを直接扱うことができるし、また、そう しなければなりません。このような直接的な操作ができるのは、データの要素へのアクセスがまったく制限され ていないからです。このようにしたのは、他に stude Ⅱ t ー i Ⅱ fo を操作するよい方法がなかったからです。 しかし、ユーザのデータアクセスを自由にするより、 student_inf 。の内部構造を隠したいと考えるかもしれ ません。特に、データへのアクセスを関数を通して行うようにするとよいかもしれません。こうするためには、 ユーザが student-inf 。を操作するのに便利なものをそろえる必要があるでしよう。これらが、この型 ( クラ ス ) のインタフェースになるのです。 そのような関数を考える前に、なぜ std::string や std::vector のような完全修飾形を使い、 using 宣 言で修飾のない名前を使えるようにしないかを考え直してみましよう。 stude Ⅱ t ー i Ⅱ f 。を使いたい場所ではこの クラスの定義が必要なので、これをへッダファイルに入れることを考えます。 4.3 で指摘したように、他のプ ログラマに使われることが予想されるファイルでは、宣言の数は必要最小限にしておくべきなのです。あきらか 、 stude Ⅱ t ー info はこれから使いたい名前なので定義しておく必要があります。しかし、その内部に string や vect 。 r があるのは、定義の内部仕様です。内部仕様のために、ユーザに using 宣言を強要することはでき ないのです。 プログラムの例として、また、よい習慣付けとしても、ヘッダファイルに入れる名前は完全修飾形で使うこと にします。ただ、ソースファイルのほうには、 using 宣言があると仮定します。したがって、ヘッダファイルに 使うつもりのないコードでは、修飾をつけない名前を使います。 9.2.1 メンバ関数 student-inf 0 のデータへのアクセスをコントロールするために、プログラマが使えるインタフェースを定 義する必要があります。ます、データを読み込む read 関数と最終成績を計算する grade 関数を定義してみま * 1 訳注 : struct で定義されるものは構造体とよばれます。構造体も ( 広い意味での ) クラスです。これは 9.3 で説明されます。