『プログラミング言語 C++ 第4版』(ビャーネ・ストラウストラップ著)第5章の学習メモ。
本章で重要と感じた・疑問に思ったポイントの要約と、理解があいまいだった用語の整理を行う。
用語メモ
| 用語 | ページ数 | 用語の意味 |
|---|---|---|
| エンドユーザー | 119 | ある製品やサービスを最終的に使用する人や組織のこと。 |
| ポインタセマンティクス | 121 | その値を直接持つのではなく、値がある場所(アドレス)を指し示すことで間接的に扱う意味論。C++でいう「ポインタ渡し」と「参照渡し」。 |
unique_lock |
125 | コンストラクタでmutexを獲得する。ほかのスレッドが、そのmutexを獲得していれば、他のスレッドが処理を完了するまで待機する。使い終わったら(スコープから抜けるなどしたら)、自動的にmutexを解放する。 |
| 型関数 | 132 | 引数あるいは返却値として、ある型が与えられて、コンパイル時に評価される関数。テンプレートの「型を入力して型を返す仕組み」。例:using P = std::add_pointer<int>::type;
|
| メタプログラミング | 132 | プログラムをコンパイル時に生成・変換・判定する仕組み。 |
| ランダムアクセス反復子 | 132 | 任意の位置(ランダム)に一気にアクセスできるイテレーター。配列風アクセス可。 |
| 前進反復子 | 132 | 前方向に一歩ずつ進めていける”ことが保証された反復子。++i, i++で次の要素にアクセス可。 |
| 値型(value type) | 133 | 標準ライブラリのイテレータやコンテナに必ず付いている「その要素の型」を表す名前。std::vector<int>::value_type → int
|
| タグ指定(tag dispatch) | 133 | 型を「タグ」として渡すことで、コンパイル時に最適な関数を選ばせるテクニック。 if で分岐させる代わりに、型の違いを使って分岐する。 |
| 型述語 | 134 | ある型について真偽を返す性質(bool値)。std::cout << std::is_integral<int>::value << "\n"; // 1 (true)
|
| 原文字列リテラル | 136 |
R"(で始まって、)"で終わる文字列。逆斜線や引用符を直接文字列内に記述できる。 |
| スカラ | 137 | 「配列や構造体のように複数の要素をまとめた型」ではなく、ひとつの値として扱える型のこと。bool, int, double,ポインタ型など。 |
| ハンドル | 140 | 資源を所有・管理するためのオブジェクトのこと。 |
| 多相型 | 140 | 仮想関数を持つ型(動的多態性の対象) |
重要・疑問ポイントまとめ
コンストラクタとデストラクタを組み合わせることで、オブジェクトが消滅した際に、資源だけが残らないことが保証される。(p120)
ポインタセマンティクスが本当に必要な場面では、unique_ptrは極めて軽量だし、組込みのポインタを適切に使うのと比べて空間と時間のオーバーヘッドも発生しない。(p121)
shared_ptrは、デストラクタによる資源管理を維持しながら、ある種のガベージコレクションの役割を果たす。(p121)
本当に所有権を共有する必要があれば、
shared_ptrを用いるべきである。(p121)
コストが極端に高いわけではないが、必要な場面でだけ使うようにする。
(スマートポインタよりも)コンテナやほかの型が、自身の持つ資源をより高次元の概念レベルで管理するほうが優先だ。(p122)
(unique_ptrなどの)スマートポインタよりも、(vectorやthreadなどの)資源ハンドルを優先する。関数からオブジェクトに集合を返す際には、ポインタではなくコンテナを使ったほうが効率的かつ簡潔に行える。
引数経由で結果を返す方法が特にエレガントであるとは私は思わない。(p125)
標準ライブラリでは、複数のロックを同時に獲得できるようにするための処理を提供している。(p126)
デッドロック回避のための対策となる。
ロックとアンロックは、どちらかというと高コストな処理である。(p126)
通信手段として、データ共有を選択すべきではない。(p126)
データ共有のほうが、引数のコピーとリターンよりも効率よいと考えている人もいるが、ロック・アンロックは高コストであり、さらに最近のマシーンはデータコピーをうまく処理する。
futureとpromiseの重要な点は、ロックを明示的に使わずに、タスク間で値を転送できるようになることだ。(p128)
基本的に、
async()は、関数呼び出しの"呼び出し部分"を"結果を得る部分"から分離したうえで、自身のタスクから、それらを独立させる。async()を使うと、スレッドやロックの考慮が不要となる。(p131)
ロックが必要な資源を共有するタスクに対しては、
async()を使ってはならない。(p131)
async()はスレッドを生成する場合としない場合(遅延実行する場合)がある。(※明示的に指定することも可能。)
そもそも関数や型は、複雑なものでなくてよいし、他の多数の関数や型と密接な関係を持たなくてもよい(p131)
(clockやpairなどの小規模な)コンポーネントのほとんどが、標準ライブラリのほかのコンポーネントを含めた、より強力なライブラリ機能の構成要素として働く。(p131)
時間を実測しない限りは、コードの"効率性"について語るべきではない。性能に対する単なる推測は、まったく信用できないものである。(p132)
時間の計測例として、本書ではhigh_resolution_clock::now()とduration_castの組み合わせを使っている。
汎用的なコードを書く際には、整合的な名前のほうがありがたいものだ。(p135)
例:pairの第一メンバをfirst、第二メンバをsecondとする。
pairはインターフェースの中でよく使われる。というのも、たとえば、結果と、結果の性質を示す値のように、複数の値を返却することがよくあるからだ。3個以上の値を返すことは、それほど多くないので、tupleは汎用アルゴリズムの実装でよく使われる。
乱数生成関数は、以下の二つの要素で構成されている。
[1] エンジン:乱数または疑似乱数を生成する。
[2] 分布:生成した値を一定範囲の数学的分布へマップする。
ストラウストラップ先生からのアドバイス
( "→" 以降は補足説明。)
-
資源管理には、資源ハンドルを使おう。
→ 裸のnewやdeleteを使わず、スマートポインタを使う。vectorやthreadなどが使えるときはスマートポインタよりもそっちを使う。 -
多相型のオブジェクトを使う場合は、
unique_ptrを使おう。
→「多相型のオブジェクト」を扱うときは、普通は基底クラスのポインタを使う。ただ、以下のように裸のnew/deleteを使うと解放漏れの危険がある。Base* p = new Derived(); p->f(); delete p; // 必ず delete が必要 -
共有オブジェクトを使う場合は、
shared_ptrを使おう。 -
並行処理を行うときは、型安全なメカニズムを使おう。
→pthread_mutex_tを使って、ロック/アンロック忘れをしたり、deleteしたスレッドにアクセスしたりしないように、並行処理をするときもほかの資源と同じように、型安全なメカニズムを使う。 -
共有データは、最小限に抑えよう。
-
熟考なしの効率や計測に基づかない予測から、通信手段として共有データを選択しないように。
→時代は進む。過去の常識は今の常識ではない。 -
スレッドではなく、並行タスクとして考えよう。
→並行処理をスレッド単位ではなくタスク単位で設計し、ライブラリにスケジューリングや同期を任せて安全に管理しよう という指針。 -
ライブラリが有用となるためには、巨大になる必要もないし、複雑になる必要もない。
-
効率を議論する際は、プログラムの実行時間を事前に実測しておこう。
→計測にも基づかない予測は意味がない。 -
型の性質に明示的に依存するプログラムを記述することができる。
→ある型が持つ性質(例えば整数か浮動小数点か、ポインタか参照か、仮想関数を持つかなど)に応じて処理を変えられる。コンパイル時または実行時に型に基づいて分岐できる。ということ。 -
単純なパターンマッチングには、正規表現を使おう。
-
言語機能だけで重要な数値計算を行おうとしないように。ライブラリを使おう。
-
数値型の性質は、
numeric_limitsから得られる。
→numeric_limitsは型特性を取得するためのテンプレート。