C++ のエッセンス 読書ノート¶
C++ 創始者の B. Stroustrup 氏による基本書の抄録翻訳版という位置づけなのだろうか。
- 著者:
Bjarne Stroustrup
- 訳者:
柴田 望洋
- 出版社:
SB クリエイティブ株式会社
- 発行年:
2015 年
- ISBN:
978-4-7973-8477-2
- 関連 URL:
第 1 章 基本¶
1.1 はじめに¶
特に内容なし。
1.2 プログラム¶
従来どおり。
C++ はコンパイル言語である。ソースファイル、オブジェクトファイル、実行ファイルなどの諸概念の理解を確認する。
C++ 標準が定義する実体は二つに分類できる:言語の中核機能と標準ライブラリー。
C++ は静的な型付けを行う言語だ。コンパイル時点でプログラム構成要素のすべての型がコンパイラーに知られる必要がある。
1.3 Hello, World!¶
従来どおり。
最小のプログラム。波括弧、コメントなどの説明。
すべての C++ プログラムは
main()
を持つ。戻り値の説明など。インクルード、標準出力ストリーム、特殊文字、名前空間などの簡単な説明など。
すべての実行コードは直接的あるいは間接的に
main()
から呼び出されることになる。
1.4 関数¶
従来どおり。色々なことを一気に説明している。
引数の型チェックと型変換が行われることを軽く見てはならない。
関数の型は、返却型と引数型とによって決定される。クラスのメンバー関数であれば(というより、名前空間の「メンバー」関数であれば)、クラス名(というより名前空間の名前)も関数の型の一部となる。
関数の多重定義(オーバーロード)に関する簡単な説明。
1.5 型と変数と算術演算¶
宣言とは文であって、プログラム内に名前を新しく導入するものだ。
型、オブジェクト、値、変数の(用語としての)定義がここに来る。
基本型の例とハードウェア機能との対応について。特に演算子
sizeof
が型の大きさを返すものであることについて。代入時、算術演算時に、基本型から基本型への型変換がコンパイル時に必要に応じて起こる。演算対象の中でもっとも高精度のオペランドの型に変換される。
初期化の記法について。C++11 から波括弧による初期化、
initializer_list
由来の初期化ができる。著者は{}
で囲む形式のものを推奨している。縮小変換も依然として存在する。著者はやりたくないようだが、C 言語との互換性のために残した。
キーワード
auto
が明示的に型を指定する代わりに使える場合がある。本書では
auto
で宣言する場合には=
で初期化する。問題を引き起こす型変換を避ける。特殊な事情がない限り
auto
を用いるとよい。
1.6 スコープと生存期間¶
名前はプログラムテキスト内の特定の範囲でしか利用できない。この範囲をスコープという。
局所スコープ
ラムダ式の中で宣言された名前は、そのラムダ式から決定されるスコープにいると解釈する。
クラススコープ
名前空間スコープ
広域名前空間(スコープの定義上、これはスコープではない?)
1.7 定数¶
従来の const
と新機能の constexpr
の二種類の定数がある。後者の意味は《この値はコンパイル時に評価されますよ》と憶えておく。
定数式の中でも利用できる関数は
constexpr
として定義されなければならない。関数を
constexpr
とする場合には、それはできるだけ単純なものがよい。constexpr
と宣言した関数でも非定数の引数を利用できる(その場合には定数式とはならないだけ)。
1.8 ポインタと配列と参照¶
次のような構文が追加された。これらは範囲 for
文と呼ばれる:
for(auto x : v){
// ...
}
for(auto& x : v){
// ...
}
キーワード nullptr
により空ポインターを指すことができる。《古い時代のコードでは nullptr
ではなくて 0
と NULL
が用いられていた。しかし
nullptr
を使えば、整数である 0
と NULL
と、ポインタである
nullptr
を混同してしまう潜在的な危険性が排除できる》とある。
1.9 判定¶
switch
文の説明。今のところ従来どおり。
1.10 アドバイス¶
本章を含め、以降の章すべてのアドバイスという節が最後に来る。本ノートでは、個人的に大切にしたい項目に絞って書き留める。
よいプログラムを書くのに、C++ のすべての詳細を知る必要はない。
言語機能ではなく、プログラミング技法に集中しよう。
コンパイル時に評価しなければならない関数は
constexpr
宣言をしよう。型名を用いた宣言には
{}
構文の初期化子を使おう。auto
による宣言には=
構文の初期化子を使おう。0
やNULL
ではなくてnullptr
を使おう。
第 2 章 ユーザー定義型¶
2.1 はじめに¶
従来どおり。
組み込み型の定義に注意。基本型と
const
と宣言演算子とを組み合わせた型をそう定義する。組み込み型でない型をユーザー定義型と呼ぶ。
2.2 構造体¶
従来どおり。
演算子
new
,delete
と空き領域・動的メモリ・ヒープの説明がここに来る。演算子
.
と->
の意味を知る。
2.3 クラス¶
従来どおり。
クラスは型のインターフェースと実装とを独立して設けられる言語機能だ。インターフェースと言われる場合には
public
メンバーであると了解する。コンストラクターなどの説明がここに来る。
struct
とclass
の違いは従来どおり。
2.4 共用体¶
従来どおり。
union
は全メンバーが同じアドレスに割り当てられたstruct
だ。したがってメモリ領域の消費量は、それが最大のメンバーのそれと同じ量だ。
同時に一つのメンバーしか値を持たない。
union
のどのメンバーを利用しているのかを管理する責任はプログラマーにある。エラーを避けるために、メンバーへのアクセスを提供するのがよい。
2.5 列挙体¶
enum class
が目を引く。
列挙子のスコープが個々の
enum class
にあるため、別のenum class
に同名の列挙子を利用できる。enum class
は従来のenum
と比べて型付けが強力。enum class
の列挙子と整数値の混同はできない。enum class
ではデフォルトで代入、初期化、比較をサポートしている。従来の
enum
も利用可能だ。しかし著者が言うには《あまりよい動作はしない》。
2.6 アドバイス¶
予想外の動作を防ぐには、単なる
enum
ではなくてenum class
を利用しよう。
第 3 章 モジュール性¶
見るべき新機能は noexcept
と static_assert()
で間違いない。どちらも多用するべきものだ。
3.1 はじめに¶
宣言と定義を分けて考える。これは従来と変わらない。
3.2 分割コンパイル¶
従来どおり。読者が熟練者ならば飛ばしてよい。
ライブラリーは個別にコンパイルされたコードの集合体となるのが一般的だ。
ヘッダーファイル(宣言)とソースファイル(定義)に分ける。
3.3 名前空間¶
C++11 の段階では従来どおり。
最初の一行からは関数、クラス、列挙体も名前空間の一種であるととれる。
本物の
main()
は広域名前空間で定義されていて、明示的に定義される名前空間の所属物とはなっていない。名前空間は、比較的大規模なプログラムコンポーネントの用途に向いている。
3.4 エラー処理¶
型システムそのものがエラー処理の支援手段の一つだ。
3.4.1 例外処理¶
決して例外を送出しない関数は noexcept
と宣言できる。
void user(int sz) noexcept
{
// ...
}
それでもこのような関数が例外を送出するならば、標準ライブラリー関数
terminate()
が呼び出される。
3.4.2 不変条件¶
面白いと思ったのが bad_alloc
を明示的に処理するコード。プログラマーが自分で
terminate()
を呼び出せるのか。
3.4.3 静的アサーション¶
static_assert()
はコンパイル時にエラーを検出できる。定数式を引数に取る。
3.5 アドバイス¶
ヘッダーで非インライン関数を定義しないように。
ヘッダー内に
using
指令を記述しないように。エラー処理には例外を利用しよう。
開発初期の段階で、エラー処理方針を設計しよう。
例外には、組み込み型ではなくて、目的に応じたユーザー定義型を利用しよう。
すべての関数ですべての例外を捕捉する必要はない。
第 4 章 クラス¶
この章と次の章(テンプレート)はセットで読む。本章と次章はオブジェクト指向プログラミングとジェネリックプログラミングの言語仕様の記述にそれぞれ対応する。
4.1 はじめに¶
C++ 言語機能の中核はクラスである。
クラスをサポートする基本機能を具象クラス、抽象クラス、クラス階層に分けて理解する。
4.2 具象型¶
具象クラスは組み込み型のように振る舞うのが基本的だ。組み込み型のように振る舞うというのは次を意味するようだ:
オブジェクトをスタック上にも静的メモリにも他のオブジェクト内にも置ける。
オブジェクトを直接利用できる。
オブジェクトを即座に初期化できる。
オブジェクトをコピーできる。
4.2.1 算術型¶
本節では複素数をクラスとして定義し、具象型の何たるかを解説している。標準にも
complex
があるが、説明用にその簡易版という感じになっている。
効率化のため、単純な演算はインライン化する。つまり、関数呼び出しの機械語を生成させないように実装する。
デフォルトコンストラクターを定義すると、その型のオブジェクトは必ず初期化される。
クラスの内部データ表現に直接アクセスする必要がない演算は、クラス定義とは分離して記述できる。
値渡しによる引数はコピーである。したがって、呼び出し元の値に影響を与えない。
ユーザー定義演算子は、慣例にしたがって定義すること。
ちなみに、組み込み型の演算子を再定義することはできない。
4.2.2 コンテナ¶
要素の集合を保持するオブジェクトをとにかくコンテナと呼ぶ。
デストラクターの記号がクラス名の直前に記号
~
が付いたものである理由は、コンストラクターを補うものという意味合いがある。コンストラクターとデストラクターの組み合わせが多くのエレガントな技法の基礎だ。特に、C++ での資源管理技法の基本だ。コンストラクターで資源を確保して、デストラクターでそれを解放する技法をRAII と呼ぶ。これにより裸の
new
とdelete
のコード出現頻度を下げる。
4.2.3 コンテナの初期化¶
「コンテナ自身」の資源管理の次は、コンテナの内容物の管理を考える。ここで C++11 らしい話題が一つ出てくる。それだけ見ていく。
class Vector{
double* elem;
int sz;
public:
Vector(std::initializer_list<double>);
// ...
};
Vector::Vector(std::initializer_list<double> lst)
: elem{new double[lst.size()]},
sz{static_cast<int>(lst.size())}
{
copy(lst.begin(), lst.end(), elem);
};
Vector v1 = {1, 2, 3, 4, 5};
Vector v2 = {1.23, 3.45, 6.7, 8};
std::initializer_list<>
の例として頭に入れておくと良さそうなコードだ。
4.3 抽象クラス¶
抽象クラスの話題に移る。本節の内容は古典的なようなので後回し。
4.4 仮想関数¶
仮想関数テーブルの話題。これも古典的なトピックだ。
4.5 クラス階層¶
クラス階層とは派生によって束ねられるクラス群のことだ。いわゆる is-a 関係を表現するために階層構造を持つクラスを利用する。
class Shape{
public:
virtual void move(Point to) = 0;
virtual void draw() const = 0;
virtual void rotate(int angle) = 0;
virtual ~Shape(){}
};
4.5.1 明示的なオーバーライド¶
C++11 からは、関数をオーバーライドしていることを派生クラスのプログラマーが記述することができる。キーワード override
を関数宣言に付加する:
class Circle : public Shape{
// ...
};
class Smiley : public Circle{
// ...
void move(Point to) override;
void draw() const override;
void rotate(int) override;
// ...
};
4.5.2 階層の利点¶
クラス階層の利点とはインターフェース継承と実装継承の二つだ。例えば Smiley*
を Shape*
として扱えるなど。
4.5.3 階層の移動¶
演算子 dynamic_cast<>
について。これは is-kind-of, is-instance-of と考えられる。
4.5.4 資源リークの回避¶
C++11 で登場するテンプレート unique_ptr<>
について。デモコードでは
vector<unique_ptr<Shape>>
の形で利用されている。
4.6 コピーとムーブ¶
ここは大切なのでよく理解すること。
コピー演算のデフォルトの意味はメンバー単位のコピーであり、つまりメンバーそれぞれに対してコピーすることだ。
クラスを設計するときには、次の二点を必ず検討すること:
オブジェクトがコピーされる可能性があるか
コピーの方法をどうするか
抽象型のコピーがメンバー単位のコピーであることはまずない。
4.6.1 コンテナのコピー¶
コピーコンストラクターとコピー代入演算子の基本を解説。
4.6.2 コンテナのムーブ¶
ここは C++11 らしい話題なので丁寧に読んでいく。
Vector operator+(const Vector& a, const Vector& b)
{
// ...
Vector res(a.size());
// ...
return res;
}
上記コードの最後、局所変数 res
のコピーが作られた上で、呼び出し元に置かれるのだが、次のような呼び出しを考えるとコピーが複数回生じることが観察できる:
Vector r;
// ...
r = x + y + z;
この問題の本質とは、operator+()
内の res
がコピー後に利用されないことだ。この関数の呼び出し元がしたいことは res
を取り出したいくらいのことだ。
C++11 ではこの問題を次のようにして解決する:
class Vector{
public:
Vector(const Vector& a);
Vector& operator=(const Vector& a);
Vector(Vector&& a);
Vector& operator=(Vector&& a);
// ...
};
Vector::Vector(Vector&& a)
: elem{a.elem},
sz{a.sz}
{
a.elem = nullptr;
a.sz = 0;
};
// ムーブ代入も同様の実装となる
&&
は「右辺値参照」、つまり右辺値をバインドできる参照を意味する。右辺値とは、《少々不正確に説明すると、関数が返す整数などのような、代入できない値のことである》。
右辺値参照とは、《他の誰も代入を行えない何かを参照》するものと憶えておく。
ムーブコンストラクターおよびムーブ代入演算子は
const
の引数を受け取らない。ムーブ後に、ムーブ元オブジェクトはデストラクターが実行できる状態に移行する。したがって、ムーブの実装では、引数の中身を「抜け殻」にすること。
抜け殻になることを明示的にコンパイラーに教えるには
std::move()
を呼び出す。
4.6.3 基本演算¶
ひじょうに大切なことを説明しているので、本書をいちいち参照するといい。
C++11 には、特殊メンバー関数のコンパイラーによるデフォルト実装を採用することを明示する方法がある。
class Y{
public:
Y(Sometype);
Y(const Y&) = default;
Y(Y&&) = default;
// ...
};
クラスのメンバーにポインターか参照があるならば、コピー演算とムーブ演算について明示するのが望ましい。
キーワード
explicit
についていちばん基本的な用途を説明している。
4.6.4 資源管理¶
資源はメモリだけではない。
4.6.5 演算子の抑制¶
デフォルトのコピー演算、ムーブ演算を無効化する方法が一つ増えた:
class Shape{
public:
Shape(const Shape&) = delete;
Shape& operator=(const Shape&) = delete;
Shape(Shape&&) = delete;
Shape& operator=(Shape&&) = delete;
virtual ~Shape();
// ...
};
このキーワード delete
の用途が現れる以前は、これらの特殊関数を private
に明示的に宣言しておくという手法を採った。
クラス階層内のオブジェクトをコピーする必要がある場合には、別途専用のインターフェースを設ける。
デストラクターを明示的に宣言されたクラスに対しては、コンパイラーはムーブ演算を自動生成しない。
クラス階層内の基底クラスは、コピー演算の対象とはしたくない。
4.7 アドバイス¶
演算子を定義する場合は、本来の動作を模倣しよう。
左右二つの引数に対称性がある演算子は、非メンバー関数として実装しよう。
任意の
lhs
,rhs
に対してoperator@(lhs, rhs) == operator@(rhs, lhs)
なる演算ということ。
クラスがコンテナであれば、初期化子並びコンストラクターを実装しよう。
大規模クラス階層でのオーバーライドは
override
で明示しよう。コンテナは値で返却しよう(ムーブを活用できるので効率的だ)
デストラクターをもつクラスには、ユーザー定義のコピー演算とムーブ演算が必要であるか、あるいは、削除されたコピー演算とムーブ演算が必要である。
オブジェクトの構築、コピー、ムーブ、解体を制御しよう。
資源とみなせるものをリークさせてはいけない。
クラスが資源ハンドルであれば、コンストラクター、デストラクター、非デフォルトのコピー演算が必要だ。
第 5 章 テンプレート¶
5.1 はじめに¶
最初に著者はテンプレートをこう定義している。
テンプレートは、一連の型や値をパラメーター化したクラスもしくは関数であり、極めて汎用的な概念を表現する。
例えば
テンプレートに対して要素型である
double
などを引数として指定すると、その型に対応した関数が生成される。
5.2 パラメーター化された型¶
C++98 とは違って 2 個の
>
の間に空白文字を置かなくてもかまわなくなった。テンプレートはコンパイル時のメカニズムであるので、実行時オーバーヘッドが増すことはない。
標準ライブラリー用に生成されるコードは良質であることを期待してよい。
値引数は有用となり得る。
テンプレート値引数は定数式でなければならない。
5.3 関数テンプレート¶
最初の例を見て、
sum()
に対して引数型を明示的に指定する必要がないということを理解する。関数テンプレートは
virtual
なメンバー関数にはならない。その理由を理解すること。
5.4 コンセプトとジェネリックプログラミング¶
テンプレートが提供する機能が何であるかを考えると、テンプレートの活用法が理解できる。
ジェネリックプログラミングとは、汎用アルゴリズムの設計と実装と利用に集中するプログラミングを意味する。いろいろな型の何々が利用可能であるという意味に捉える。前節の sum()
を例にとって理解する。
コンセプトとは、テンプレートが実体化において、そのテンプレート引数が要求する何かを表すという解釈でいいか?
有用で優れたコンセプトは、基礎的なものであり、設計しなくても見つかるものだ。
とあるが、例に線形空間や体を挙げているので、単に著者の設計手腕が高いだけだとも考えられる。もっとも、終盤のパラグラフの説明は平易で良い。
5.5 関数オブジェクト¶
関数オブジェクト、ファンクターはテンプレートの用途で特に有用なものの一つだ。演算子 operator()
を有するクラステンプレートの形式をとる。
冒頭の関数オブジェクトの形で定義しておくと、間接的な関数呼び出しよりも効率的になる。
ラムダ式がここで示される。
count(vec, [&](int a){ return a < x; });
count(lst, [&](const string& a){ return a < s; });
表記
[&](int a){ return a < x; }
などをラムダ式と呼ぶ。これはLess_than<int>{x}
と同じ関数オブジェクトを生成する。[&]
はラムダ式内のx
は参照で用いるように指定するものだ。このように、参照で用いる変数を指定するものをキャプチャーリストと呼ぶ。x
だけをキャプチャーする場合には[&x]
と書く。すべてキャプチャーならば[&]
だ。x
をコピー生成する場合には[=x]
と書く。すべてコピーならば[=]
だ。何もキャプチャーしない場合には
[]
と書く。
ラムダ式とテンプレートをうまく組み合わせるとコレクションに対するループと収容要素に対する処理を分類しやすくなる。
5.6 可変個引数テンプレート¶
いにしえの printf()
のような感じで、任意の型、個数の引数を受け取るテンプレートを定義できる。本書の例を引用する。コード中 3 箇所に現れる省略記号 ...
に注意すること:
void f(){}
template<typename T, typename... Tail>
void f(T head, Tail... tail)
{
g(head);
f(tail...);
}
ここで関数 g()
は何か適当な関数テンプレートとする。
5.7 別名¶
別名機能が有用であるのは説明するまでもないはずだが、著者はこれを周囲からは不思議がられているらしい。
型やテンプレートに別名を付ける機能がある。それにはキーワード using
を使う。
using size_t = unsigned int;
別名付けはコードの可搬性を高めるのに利用できる。
別名をテンプレートの引数の一部またはすべてを bind して、新しいテンプレートを定義する際にも利用できる。
5.8 テンプレートのコンパイルモデル¶
この節では難しいことを述べているように見えるが、わけのわからないテンプレートを書くと、わけのわからないコンパイルエラーが出ると言っているに過ぎない。
5.9 アドバイス¶
テンプレートを活用して、コードの抽象化レベルを引き上げよう。
テンプレートを定義する際には、まず非テンプレートバージョンを設計、デバッグして、その後で、引数を追加して一般化しよう。
テンプレートは型安全だが、そのチェックはずっと後で行われる。
テンプレートは、情報を失うことなく、引数型を受け渡しできる。
テンプレートを定義する際には、テンプレート引数に想定されるコンセプト(要件)を熟慮しよう。
ある特定の箇所でのみ必要とされる単純な関数オブジェクトが必要であれば、ラムダを使おう。
同種の引数の並びに対して可変個引数テンプレートを利用しないようにしよう(初期化子並びを優先しよう)。
テンプレートは、コンパイル時「ダックタイピング」を提供する。
第 6 章 ライブラリの概要¶
6.1 はじめに¶
本質に集中することが重要であって、詳細の理解が不足していることに惑わされることはない。
C++ 標準では 2/3 を標準ライブラリーの仕様に割いている。
6.2 標準ライブラリのコンポーネント¶
著者が標準ライブラリーを機能に基づいて分類した一覧が掲載されている。それから、クラスをライブラリー化する判定基準を述べている。
クラスが C++ プログラマーの熟練度によらずに有用であること
特別なオーバーヘッドを必要としないこと
クラスの単純な利用方法が容易に学習できること
6.3 標準ライブラリヘッダと名前空間¶
標準ライブラリー機能は名前空間
std
の中で定義されている。本書では次の二点を明示することがないことがほとんどだ:
std::
#include
ある名前空間のすべての名前を広域名前空間に持ち込むのはお粗末とされる。つまり
using namespace std;
とは、一般的にはお粗末だと言っている。
最後に名前空間 std
内の宣言をもつ標準ライブラリーのヘッダーファイルの一部の一覧がある。この時点で今 (C++98/03) まで見たことがないものがある。
<array>
, <chrono>
, <forward_list>
, <future>
, <random>
,
<regex>
, <thread>
, <unordered_map>
.
<stdlib.h>
のような標準 C のライブラリーも提供されている。これのstd
バージョンは<cstdlib>
となる。他の標準 C のライブラリーにも同様の対応物がある。
6.4 アドバイス¶
標準ライブラリーが万能であると考えないように。
これは標準以外のライブラリーも調べてくれという意味にとる。
第 7 章 文字列と正規表現¶
7.1 はじめに¶
C++ の正規表現は近代的な言語のほとんどと似た形式のものだ。
string
オブジェクトとregex
オブジェクトは、Unicode を含むさまざまな文字型をサポートしている。
7.2 文字列¶
《標準の
string
はムーブコンストラクタを実装しているので、長いstring
を値で返す処理は、効率よく行われる》
7.2.1 string
の実装¶
《近年、string
は、短い文字列の最適化という手法で実装されている。これは、短い文字列の値を string
オブジェクト自身の中に保持しておき、長いものを空き領域に置くというものだ》
7.3 正規表現¶
《標準ライブラリは
<regex>
で、regex
クラスと、それを補助する関数とで正規表現のサポートを提供する》パターンの表現には Python のように原文字列リテラルを利用するといい。 C++ では次のように文字列リテラルを定義することもできる:
R"(pattern)"
<regex>
が提供する主な機能:regex_match()
regex_search()
regex_replace()
regex_iterator
regex_token_iterator
7.3.1 探索¶
関数
regex_search()
はbool
値を返す。引数のsmatch
オブジェクトに結果を格納する。このオブジェクトは《一致部分のstring
型を要素とするvector
である》。
7.3.2 正規表現の表記¶
正規表現には「方言」がいろいろある。C++ の正規表現ライブラリでは ECMAScript で利用されている ECMA 標準の変種をデフォルトの「方言」として採用している。
このサブセクションは正規表現のメタキャラクターに関する説明に終始しているので省略。
7.3.3 反復子¶
sregex_iterator
のコンストラクター呼び出しで正規表現の検索をする。sregex_iterator
はregex_iterator<string>
のことだ。
regex_iteartor
は双方向反復子なので、入力ストリームに対する反復処理を直接的に行うことはできない。sregex_iterator
のデフォルトコンストラクターが返す反復子がend()
相当。
7.4 アドバイス¶
C 言語スタイルの文字列関数よりも、
string
処理を優先しよう。string
を返す場合は、(ムーブセマンティクスに基づいて)値で返却しよう。どうしても必要ならば(どうしても必要な場合に限り)、
string
の C 言語スタイル文字列表現の生成にc_str()
を利用しよう。きわめて単純なパターンでなければ、正規表現の記述には原文字列を優先しよう。
正規表現の文法は、さまざまな標準に準拠するように細かく制御できる。
ストリームに対してパターンを反復して探すには
regex_iterator
を使おう。
第 8 章 入出力ストリーム¶
8.1 はじめに¶
入出力ストリームは、テキストや数値を書式あり・なしでバッファリングする入出力機能と考えられる。
ostream
はオブジェクトを文字・バイトのストリームに変換する。反対にistream
は文字・バイトのストリームをオブジェクトに変換する。これらの処理は型安全・型付けがされているだけでなく、ユーザー定義型を処理するように拡張することも可能だ。
それ以外の形態の入出力は標準の範囲外だ。
本書では扱われないが、標準ストリームはロケール依存であり、高度なバッファリング手法を採用している。
8.2 出力¶
目新しいことはないようなので省略。
8.3 入力¶
ここも従来と変わりはない。
《デフォルトでは、スペースなどの空白類文字は読み取りを終了させる。(略)末尾の改行文字までの行全体を読み取る場合は
getline()
関数を使う》
8.4 入出力の状態¶
ここも従来と変わりはない。
iostream
は状態を持っている。ストリームオブジェクト自身がbool
に変換される。cin >> i
をif
文の条件部に書くこともできる。cin.eof()
,cin.fail()
など、直接状態を問い合わせるメンバーもある。cin.clear()
で状態を勝手にリセットできる。cin.setstate(ios_base::failbit)
などとすることで状態を勝手にセットできる。
8.5 ユーザ定義型の入出力¶
ここも従来と変わりはない。
出力演算子のオーバーロードは単純に書ける。一方、《入力演算子のそれは書式の確認やエラー処理が必要なので、少し複雑になる》。
サンプルコードでは最終的にストリームオブジェクトのフラグを失敗にマークする場合がある。例外を送出することはできないのだろうか。
入力演算子は
istream
への参照を返すので、それをうまく使って入力のオーバーロードを実装する。is.get(c)
は空白文字を読み飛ばさない。
8.6 書式化¶
もっとも単純な書式化の制御は、操作子によって行える。定義されているヘッダーファイルが複数にばらけている:
<ios>
<istream>
<ostream>
<iomanip>
: 引数を受け取る操作子が定義されている。
浮動小数点数値の出力書式を学ぶ。
一般書式
defaultfloat
: 処理系に適当な書式を選択させる。これが C++11 機能。科学技術書式
scientific
固定書式
fixed
浮動小数点数値は丸められる。
precision()
は整数に影響しない。浮動小数点数の書式は有効性が持続する。一度出力してリセット、ではない。
8.7 ファイルストリーム¶
<fstream>
の提供する機能の話題だが、ここで述べられていることは iostream
の機能に過ぎない。
8.8 文字列ストリーム¶
<sstream>
の提供する機能の話題だが、ここで述べられていることは iostream
の機能に過ぎない。
8.9 アドバイス¶
>>
はデフォルトでは空白文字を読み飛ばす。回復できる可能性がある入出力エラーを処理するには、ストリーム状態
fail
を調べよう。ファイルストリームのコピーを試みないようにしよう。
メモリ上で書式化するのであれば、
stringstream
を利用しよう。
第 9 章 コンテナ¶
9.1 はじめに¶
コンテナとは、オブジェクトを内部に保持することを目的とするクラスのことだ。
9.2 vector
¶
標準コンテナの中で最も有用。
オブジェクト初期化のコードが中括弧でなされているので注意(このクラスに限った話ではないが)。
範囲
for
ループが利用できる。《標準ライブラリの
vector
を使っているのは、push_back()
を繰り返したときの効率がよいからだ》からのメモリと要素の確保の基本についてはよく読んでおく。《私は
reserve()
を性能向上のために使ったことがある。しかし、無駄な努力であることが判明した》。要素の再確保を回避するときだけに使うようだ。
コピーが望ましくないときは、参照やポインタ、あるいはムーブ演算を使う。
9.2.1 要素¶
標準ライブラリーのすべてのコンテナーについて、要素の型は任意だ。
新しい要素をコンテナーに追加する際は、その値がコンテナー内にコピーされる。
仮想関数クラスを利用したクラス階層を使っているのであれば、オブジェクトを直接保持させてはならない。ポインターまたはスマートポインター(後述)を保持させる。
vector<Shape*> vps;
vector<unique_ptr<Shape>> vups;
9.2.1.1 範囲チェック¶
標準ライブラリーの
vector
はoperator[]()
は範囲チェックを保証しない。at()
は引数が要素の範囲を越えている場合にout_of_range
例外を送出する。
main()
を try
ブロックとして定義する方法にも触れられている。
9.3 list
¶
要素を移動することなく、要素の挿入や削除を行う必要があるシーケンスに対して利用するものだ。
要素数が少ない場合は
list
よりもvector
のほうが性能がよい。走査やソートと探索などでは
vector
のほうが性能が高い。
9.4 map
¶
map
は連想配列や辞書などと呼ばれることもあり、平衡二分木として実装される。map
は探索に特化されている。角括弧よりも
find()
やinsert()
を使うと、不意に値が追加されることを避けられる。
9.5 unordered_map
¶
map
の探索コストは対数オーダーであり、効率的ではあるのだが、順序判定を必要としないハッシュベースの探索のほうが効率は優る。unordered_map
のインタフェースはmap
とよく似ている。というか、同じでないとおかしい。ハッシュ関数を自作することもできる。その場合にはクラステンプレートの引数に自作関数を指定する。
9.6 コンテナのまとめ¶
非順序コンテナはキーによる探索用に最適化されている。
《
queue<T>
,stack<T>
,priority_queue<T>
というコンテナアダプタを提供する》。これらの内部に標準コンテナが含まれている。標準コンテナとその基本的な処理は、コンテナが異なっていても記法と意味が画一であるように設計されている。
forward_list
は空のシーケンスに対して最適化が行われている。《意外にも便利だ》そうだ。
9.7 アドバイス¶
デフォルトのコンテナとして
vector
を利用しよう。要素数を変更した
vector
に対して、反復子を利用しないように。map
は、一般的に、赤黒木として実装される。unordered_map
は、ハッシュ表である。コンテナは、参照渡しで与えて、値で返却しよう。
コンテナの要素数指定には
()
構文の初期化子を利用して、要素の並びの指定には{}
構文を利用しよう。メモリ上で連続するコンパクトなデータ構造を優先しよう。
list
の走査は、比較的高コストである。要素の型が自然な順序をもたない場合は、非順序コンテナを利用しよう。
標準ライブラリのコンテナを熟知して、手作りのデータ構造よりも優先させよう。
第 10 章 アルゴリズム¶
10.1 はじめに¶
標準アルゴリズムは半開区間の要素のシーケンスを処理する。それは先頭要素を指す反復子と、末尾要素の直後を指す反復子とで表現される。
標準ライブラリの
list
はムーブコンストラクタをもっているので、このコードのような``return`` 文が効率よく行われる。
10.2 反復子の利用¶
begin()
とend()
がいちばん基本的な反復子だ。標準ライブラリの探索アルゴリズムの多くが、見つからなかったことを伝えるために
end()
を返す(正確に言うと、引数として渡した半開区間の終端を指す反復子を返す)。反復子を使うと、アルゴリズムとコンテナが分離できる。このモデルにより汎用性と柔軟性が高まる。
アルゴリズムはデータが格納されているコンテナについては何も知らない。
コンテナは、データに適用されるアルゴリズムについては何も知らない。
10.3 反復子の型¶
特定の反復子の型をユーザーが意識しなければならない場面はほとんどない。
10.4 ストリーム反復子¶
ストリームが値のシーケンスを読み書きすることから、反復子の概念をストリームに適用できる。
ostream_iterator
を作るには、出力ストリームと出力オブジェクトの型の両方の指定が必要だ。出力ストリームはコンストラクターの引数とする。
出力オブジェクトの型をテンプレート引数とする。
istream_iterator
も同様だ。ただし終端についてはストリームの指定をできない。これらの反復子を p. 118 のように直接利用することはほとんどない。アルゴリズムの引数として与えるのがふつうだ。
デモコードの一時変数についての書き換えについて。C++11 から中括弧でコンストラクターを呼び出せるようになったことが実は大きいのでは?従来だと p. 120 のコードを丸括弧で書くとダメコンパイラーが文句を言ったと記憶している。
10.5 述語¶
処理をアルゴリズムの引数とすることもできる。特に bool
値を返すようなものを述語という。
述語の形式には関数、関数オブジェクト、ラムダ式が考えられる。
auto p = find_if(
m.begin(), m.end(),
[](const pair<string, int>& r){ return r.second > 42; });
10.6 アルゴリズムのまとめ¶
《アルゴリズムの一般的な定義は、“特定の問題を解くための一連の演算を提供する有限個の規則であり、しかも五つの重要な機能である有限性、確定性、入力、出力、効率性をもっているもの”(Knuth, 1968) である。 C++ 標準ライブラリでのアルゴリズムの定義は、要素のシーケンスを処理するための関数テンプレートである》。これは諳んじられるようにしておきたい。
ヘッダーファイル
<algorithm>
に数十ものアルゴリズムが定義されている。標準アルゴリズムは、入力シーケンス一つを受け取るのに半開区間で表される二つの反復子を引数に取る。
標準アルゴリズムの多くが、コンテナ、文字列、組み込み型の配列に適用できる。
コンテナ内の要素を加えたり取り除いたりするアルゴリズムはない。アルゴリズムはコンテナを知らない。
10.7 コンテナアルゴリズム¶
シーケンスを一対の反復子で扱うことで、汎用的かつ柔軟さを得られる。
コンテナ全体に対してアルゴリズムを適用することが多いが、それが望みなら自作できる。
10.8 アドバイス¶
ループを記述する際は、汎用アルゴリズムとして表現できるかどうかを検討しよう。
述語は、引数を更新してはならない。
標準アルゴリズムを理解して、手作りのループよりも優先しよう。
第 11 章 ユーティリティ¶
11.1 はじめに¶
《小規模だが幅広く有用な》標準コンポーネントを見ていく。
11.2 資源管理¶
本書では資源を次のように定義している:《利用するために獲得して、利用後に暗黙的あるいは明示的に解放するもの》。
《標準ライブラリのコンポーネントは、資源リークを発生させないように設計されている。(略)コンストラクタとデストラクタを組み合わせることで、オブジェクトが消滅した際に、資源だけが残らないことが保証される》。この技法が資源管理の基本だとある。
11.3 特殊化されたコンテナ¶
STL の定めるコンテナ要件と完全に合致しないようなコンテナがいくつかある。著者はこれを妥当性には欠けるものの almost container と呼んでいる。
組み込み配列
array<T, N>
bitset<N>
vector<bool>
pair<T, U>
tuple<T...>
basic_string<C>
valarray<T>
11.3.1 array
¶
《
array
は、要素数が固定されて、想定外にポインタ型への変換が行われることがなくて、僅かではあるものの有用な関数を提供する組み込み配列とみなすとわかりやすい。組み込み配列と比較して(時間的あるいは空間的な)オーバーヘッドもない》《私が
array
を採用する主な理由は、想定外にポインタへと変換されて困ってしまう事態を避けるためだ》
11.3.2 bitset
¶
従来と変わらないようだ。
bitset<N>
のサイズN
の値がコンパイル時に既知である必要がある。long long int
に収まらないビットセットは整数を直接利用するのはキツイ。ビットに対して番号ではなく名前でアクセスしたいならば
set
や列挙体を採用するほうがよい。
11.3.3 pair
と tuple
¶
前者は従来と変わらないようなのでノート略。
tuple
は異種要素のシーケンスだと言っているので Python でいうtuple
と同格の存在だろう。関数
make_tuple()
でオブジェクトを生成するといい。要素を取り出すのに例えば
get<1>(t)
のような《見苦しい記述》をする。
11.4 時間¶
《時間を処理する標準ライブラリ機能は <chrono>
で、std::chrono
部分名前空間の中で定義されている》
11.5 関数アダプタ¶
関数アダプタを次のように説明している:《関数を引数として受け取って、その関数を実行する関数オブジェクトを返す》。つまり機能としては関数だ。
カレー化、部分評価と呼ばれるものだ。
《バインダは過去に多用されていたが、それらの大部分の用途では、ラムダ式を用いることで、より容易に記述できると考えられる》。そうなのか。
11.5.1 bind()
¶
using namespace placeholders;
《多重定義した関数の引数をバインドするには、バインド対象がどの関数であるのかを明示する必要がある》。このコード片だと旧式キャストを適用することになる。
bind()
の結果を保持するならばauto
として宣言した変数に対して代入するのがよい。
11.5.2 mem_fn()
¶
mem_fn(mf)
の形でフリー関数として呼び出される関数オブジェクトを生成する。標準アルゴリズムがフリー関数の呼び出しを前提としているので、こういうものが提供される。
《バインダの代わりに、簡潔で汎用的なラムダ式が利用できることも多い》
for_each(v.begin(), v.end(), mem_fn(&Shape::draw));
for_each(v.begin(), v.end(), [](Shape* p){ p->draw(); });
11.5.3 function
¶
function<int(double)> f = round;
《標準ライブラリの
function
は、呼出し演算子()
によって呼び出せる任意のオブジェクトを保持する型だ。すなわち、function
型のオブジェクトは、関数オブジェクトである》コールバックや処理を引数に渡す場合に有用。
11.6 型関数¶
型関数とは関数であって、次の条件を満たすものを指す:
引数か返却値として型が与えられるもの
コンパイル時に評価されるもの
11.6.1 iterator_traits
¶
タグディスパッチの解説。時間がないので略。
11.6.2 型述語¶
ヘッダーファイル <type_traits>
に、型に関する基本的な情報を返すだけの単純な型関数が提供されている。これらの機能はテンプレートを作成する際に有用となる。
is_class
,is_pod
,is_literal_type
has_trivial_destructor
is_base_of
etc.
11.7 アドバイス¶
取得して解放するものは、すべて資源である
資源管理には、資源ハンドルを使おう (RAII)
shared_ptr
よりもunique_ptr
を優先しようしばしば、ラムダは
bind()
やmem_fn()
の代替となる
第 12 章 数値演算¶
12.1 はじめに¶
《より複雑なデータ処理では、C++ の強力な機能が真価を発揮する》
12.2 数学関数¶
ヘッダーファイル
<cmath>
に標準数学関数と呼ばれる関数がある。表によると絶対値、数値を丸める関数、平方根、三角関数、逆三角関数、双曲線関数、逆双曲線関数、指数関数、対数関数が勢ぞろいだ。引数型として次の組み込み型がサポートされている。
float
double
long double
《エラーは、
<cerrono>
が定義するerrono
への代入によって通知される。定義域エラーならばEDOM
であり、値域エラーならばERANGE
である》。これは知らなかった。大域変数を見に行く必要があるとは。
最後で触れられている特殊数学関数とは、微分方程式の教科書などで紹介されているような一群の関数のことだと思われる。
12.3 数値アルゴリズム¶
ヘッダーファイル <numeric>
は汎用の数値アルゴリズムを提供している。
accumulate()
: 和inner_product()
: スカラー積partial_sum()
: 部分和adjacent_difference()
: 階差数列iota()
: Python のrange()
のようなもの
《シーケンスの要素に対して演算をパラメータ化して適用することも可能だ》。
12.4 複素数¶
ヘッダーファイル <complex>
について。
クラステンプレート
complex
の実部と虚部がテンプレートになっているので、float
でもdouble
でもサポートされる。また、複素数に対する一般的な算術演算および数学関数も提供されている。
12.5 乱数¶
乱数機能は C++11 で変貌を遂げたようだ。
標準ライブラリの
<random>
では、多様な乱数生成関数が提供されている。乱数生成関数は、以下の二つの要素で構成されている:
エンジン :乱数または疑似乱数を生成する。
分布 :生成した値を一定範囲の数学的分布へとマップする。
まず p. 144 のコードを見ると、もうわけがわからない。しかし急所を書き換えてくれてある:
auto die = bind(
uniform_int_distribution<>{1, 6},
default_random_engine{});
こうすることで呼び出し die()
が 1 から 6 までの出目を無作為抽出するようになる。
《初心者にとっては、乱数生成ライブラリのインタフェースが完全に汎用化されていることが、大きな障害となり得る。そのため、単純な一様乱数の生成から始めるとよいだろう》
12.6 ベクタの算術演算¶
vector
には算術演算がサポートされていない。《標準ライブラリ
<valarray>
でvector
に似たテンプレートvalarray
を提供している。これは汎用性を低めることによって、数値演算を最適化しやすくするものだ》
12.7 数値の限界値¶
ヘッダーファイル <limits>
には組み込み型の性質を表すクラスが提供されていて、p. 146のコード片のようなコンパイル時診断を可能とする。
12.8 アドバイス¶
数値演算は技巧的なものとなりがちだ。
言語機能だけで重要な数値演算を行おうとしないように。
乱数生成器を得るには、乱数エンジンに分布をバインドしよう。
数値型の性質は
numeric_limits
から得られる。
第 13 章 並行処理¶
13.1 はじめに¶
《標準ライブラリの基本的な目標は、システムレベルの並行処理のサポートであって、洗練された高レベルの並行モデルを直接提供することではない》
標準ライブラリは単一アドレス空間における複数スレッドの並行実行をサポートする。
適切なメモリモデル
一連のアトミック処理
《タスクが逐次的に実行できるのであれば、それが単純で高速になるものだ》
13.2 タスクと thread
¶
タスク とは他の処理と並行的に実行される可能性のある処理のことを言う。
スレッド とは一つのプログラムにおけるシステムレベルのタスクを意味する。
複数のタスクを並行的に実行するには、タスクそれぞれから thread
を生成することで行うことができる。pp. 150-151 のコード片参照。
スレッド群は同一のアドレス空間を共有する。cf. プロセス
そのためスレッド間通信が共有オブジェクトを介することで行える。ただし、データ競合を防ぐことを考えなければならない。ロックやその他のメカニズムにより何らかの同期処理を施すのがふつうだ。
タスクを定義する目的は、タスク同士を完全に分離することだ。
共有データを一切使用しないことは、データ競合を起こさないことを意味する。
13.3 引数の受渡し¶
タスクの入力は関数の実引数という形式でなされるのが自然だが、複数のタスクで同一のデータを参照すると困ったことになるだろう。他方、値渡しでは困ったことにはならない。
《
<functional>
が定義する型関数ref()
は、可変個引数テンプレートがsome_vec
をオブジェクトではなく参照として扱えるようにするために、不本意ながら必要となるものである》
13.4 結果の返却¶
《やや姑息なのだが、結果の返却手段として引数が使われることは、珍しくない》。すなわち、タスクを表す関数の引数の一部がポインターや非
const
参照などで定義されている。《引数経由で結果を返す方法が特にエレガントであるとは私は思わない》。まったく同感だ。
13.5 データの共有¶
複数のタスクが同一のデータを共有しなければならないとき、それへアクセスするタスクを高々一つに制限する必要がある。そのための手段の一つに相互排他オブジェクト
mutex
を使える。
mutex
型のオブジェクトをより広い?スコープに定義する。これをm
とする。「制限区間」をスコープにして
unique_lock<mutex> lck{m};
として RAII する。この RAII オブジェクトの役割は直観的に理解できる。つまり、プログラマーは
mutex
オブジェクトと共有データを対応付けることになる。その管理に注意しろ。
デッドロックを回避するための技法を p. 154 で例示している。遅延ロックとでも呼べるような技法があるようだ。これによると、どこかで関数 lock()
が提供されていて、おそらく可変個の RAII オブジェクトを引き渡すことができる。その結果、指定されたすべての相互排他オブジェクトのロックを獲得する。
共有データによる通信はきわめて低レベル。複数タスクのどれが完了しているのかを判断するのが厳しいから。
ロック・アンロックはどちらかというと高コストな処理だ。
《通信手段として、データ共有を選択しないようにしよう》
13.6 イベント待ち¶
スレッドは何らかの外部イベントの完了を待たねばならないことがある。
this_thread
は唯一のスレッドを表す。外部イベントによる通信機能は
<conditional_variable>
が定義するconditional_variable
で提供される。この概念は Python のそれと同等だと考えていいだろう。《
conditional_variable
を使うと、エレガントで効率のよい数多くの共有法が実現できるものの、若干トリッキーなものとなる》
古典的な生産者・消費者のデモコード。これも Python で書くとこういう感じになるだろう。ただし consumer()
の lck.unlock()
の呼び出しに注意。キューの中身を取り出した直後に解放して、それから中身を処理するという構造をよく覚えておくこと。
mcond.wait()
をするのは消費者でmcond.notify_one()
をするのは生産者。消費者側のロック区間では
mcond.wait()
とキューからメッセージを取り出す。生産者側のロック区間ではキューへメッセージを置くことと
mcond.notify_one()
を呼び出す。
13.7 タスク間通信¶
<future>
で定義されている三つのタスク処理機能を説明している。
future
とpromise
packaged_task
async()
13.7.1 future
と promise
¶
ロックを明示的に使わずに、タスク間で値を転送できるようにするのが重要だ。
送信側のタスクが受信側のタスクに値を転送するときには、それを
promise
の中に入れる。処理系がそれを対応するfuture
に置くので、受信者はそれを読み取れるという構造だ。future
のget()
で値を取る。《値がまだ置かれていなければ、そのスレッドは、値が到着するまでブロックされる》。なるほど。
promise
にはset_value()
とset_exception()
が提供されている。送信側タスクのコードは p. 157 の関数
f()
の構造を一般的にとるものと思われる。受信側タスクは関数
g()
の構造になる。例外処理を必要としなければ、try
ブロックはないだろう。
13.7.2 packaged_task
¶
型 packaged_task
は複数の promise
と future
を連携する複数タスクの準備に利用する。p. 158 のコードによると
packaged_task
オブジェクトをタスクを表す関数から生成する。タスクの個数ぶん生成する。future
オブジェクトをスレッド開始前に生成する。それはpackaged_task::get_future()
そのものだ。thread
のタスクを表す引数にpackaged_task
オブジェクトをmove()
して渡す。結果をさきほど
.get_future()
から生成しておいたオブジェクトから.get()
する。このコードが明示的なロックを含まないことに注意する。
《なお、move()
処理が必要となっているのは、packaged_task
がコピーできないからだ。packaged_task
がコピーできないのは、それが資源ハンドルだからである》
13.7.3 async()
¶
《非同期に実行される可能性があるタスクの起動には、
async()
が利用できる》《
async()
を使うとスレッドやロックの考慮が不要となる》が、むしろ《ロックが必要な資源を共有するタスクに対してはasync()
を使ってはいけない》。《
async()
ではthread
がいくつ起動されるかが分からない》
13.8 アドバイス¶
可能な限り、高い抽象化レベルで作業しよう。
逐次実行のほうが、並行実行よりも簡潔かつ高速な場合もある。
データ競合を避けよう。
可能であれば、明示的なデータ共有は避けよう。
thread
とmutex
を直接利用するのではなく、packaged_task
とfuture
を優先しよう。単純なタスクの起動には、
async()
を利用しよう。
第 14 章 歴史と互換性¶
14.1 歴史¶
本書著者が C++ を考案、言語仕様を作成、処理系を開発した。
14.1.1 時系列¶
C++11 を完全に実装した最初の処理系が登場したのは 2013 年。
《開発中の C++11 は、C++0x という名称で知られていた》
14.1.2 黎明期¶
C++ という名前は 1983 年夏に Rick Mascitti が発案して、これを著者が採用した。
C++ は ++C よりも格下(式の値からそうだと理解できる)。
初期の頃は文書化された設計書はなかった。設計、文書化、実装が同時進行していた。
例外処理が生み出されることになった原因をもっと細かく説明して欲しい。
《仮想関数という形で実行時多相性をサポートしたことが、もっとも議論を呼んだ》。これはかなり意外に思う。
例外処理設計の重点とは、多段階の伝播、エラーハンドラーに任意の情報を渡せること、 RAII との統合の三点だ。
STL は C++98 のもっとも重要な革新だった。
14.1.3 ISO C++ 標準¶
《C++03 という名前を聞いたこともあるかもしれないが、本質的には C++98 と同じものだ》
14.2 C++11 の新機能¶
ここに列挙されているだけで言語機能と標準ライブラリーコンポーネントがそれぞれ 41, 27 項目ある。
14.2.1 言語機能¶
これはどこかに表があるはずなので、そちらを参照する。
14.2.2 標準ライブラリコンポーネント¶
これはどこかに表があるはずなので、そちらを参照する。
C++11 での追加内容は、新規コンポーネントと、既存コンポーネントの改善とに分類される。
14.2.3 非推奨とされた機能¶
これはどこかに表があるはずなので、そちらを参照する。
《非推奨機能は、将来的には削除されることになるはずだ》
14.2.4 キャスト¶
《C 言語形式キャストは、名前付きキャストの導入時に非推奨とすべきだった》。自作プログラムから全廃することを真剣に検討すべきだとまで言っている。
すべてのキャストは設計を汚すものだと考えるようにしよう。
14.3 C と C++ の互換性¶
14.3.1 C 言語と C++ は兄弟¶
C89, C99, C++98, C11, C++11 の包含?関係を p. 173 のベン図で模式的に表現しているが、かなり微妙。
14.3.2 互換性にかかわる問題¶
C 言語のコードは、C 言語としてコンパイルした上で
extern "C"
のメカニズムによって結合できる。C 言語のプログラムを C++ に変換する際に、大きな問題がある。
14.3.2.1 スタイルの問題¶
コードの基本構造 10 選。いくつか抜粋しておく。
《マクロによる置換はほぼ確実に不要である》。
定数を表すには
const
,constexpr
,enum
,enum class
を使う。関数呼び出しのオーバーヘッド排除には
inline
を使う。関数と型のファミリーを表すには
template
を使う。名前の衝突を排除するのに
namespace
を使う。
必要になるまでは変数を宣言しない。宣言と同時に初期化する。
単純に裸の
new
,delete
に置き換えないようにする。void*
,union
, キャストは利用しない。ポインター演算は使わない。
14.3.2.2 void*
¶
malloc()
を new
で書き直したいという話題だ。
14.3.2.3 C++ のキーワード¶
C 言語ではキーワードではない C++ のキーワードの一覧。本書で説明されていないものは知らなくていいということになる?
14.3.2.4 結合¶
これはかつて業務でやったので理解している。
14.4 参考文献¶
多過ぎる。
14.5 アドバイス¶
《経験豊富な C++ プログラマが何年間も見落とすことが多いのは、新機能ではなく、むしろ、機能間の関係の変化、基礎的な新しいプログラミングテクニックを実現可能にするための機能間の関係である。換言すると、初めて C++ を学習した際に思いもよらなかったことや、当時は実現不可能と考えたことが、現在は優れた方式となっている可能性があるのだ。それを見つけるには、基本をもう一度吟味するしかない》