C++

『Effective C++第3版』のメモ

More than 1 year has passed since last update.

作成中

イントロダクション

  • 呼び出し時に引数を1つだけとるコンストラクタは、explicitを付ける
    • 暗黙の型変換(T obj = 10;)を封印するため
  • 「T t1 = t2;」は、コピー代入演算子ではなく、コピーコンストラクタが呼ばれる(オブジェクトが生成されるため)
  • 引数にクラスを値渡しするとコピーコンストラクタが呼び出されてしまうので、const参照渡しにすること

第1章 C++に慣れよう

1項 C++を複数の言語の連合とみなそう

  • C、オブジェクト指向C++、テンプレートC++、STLの4つに分けると理解しやすい

2項 #defineより、const、enum、inlineを使おう

  • #defineよりconstの方が、エラー時のシンボルがわかる、必ず1つのオブジェクトになる、privateにできる
    • クラス内では、static const とする
  • enumハックとは、クラス内での定数定義。static constで初期値を設定できないコンパイルの時に使う
class A {
private:
    enum { Num = 5 }; // enumハック

    int scores[Num];
  • #defineの関数はインクリメント等で問題を起こすことがあるので、インライン関数を使う

3項 可能ならいつでもconstを使おう

const char * const p = 変数
  • 上記は、前のconstがデータ、後ろのconstがポイントを変更不可にする

4項 オブジェクトは、使う前に初期化しよう

  • C++では、組み込み型は初期化される場合とそうでない場合があるので、自分で初期化すること
  • メンバ変数は、コンストラクタ内で代入するのではなく、「メンバ初期化子リスト」を使って初期化すること
    • なぜならユーザ定義型は、デフォルトコンストラクタで初期化され、その後にコンストラクタで代入されることになり無駄が生じる
  • 初期化順は必ずメンバ変数の宣言順になるため、メンバ初期化子リストに買く場合も宣言順に書くべき
  • 継承していた場合は、必ず基底クラス、派生クラスの順番に初期化される
  • ローカルでないstatic変数の初期化順は不定なので、その問題を避けるようにすること

2章 コンストラクタ、デストラクタ、コピー代入演算子

5項 C++が自動で書き、自動で呼び出す関数を知ろう

  • デフォルトコンストラクタ、コピーコンストラクタ、デストラクタ、コピー代入演算子は暗黙的に生成されるかもしれない

6項 コンパイラが自動生成することを望まない関数は、使用を禁止しよう

  • コピーコンストラクタ、コピー代入演算子をprivate宣言すれば使用禁止にできる
class Sample {
private:
    Sample(const Sample&);
    Sample& operator=(const Sample&);
}
  • また、以下のようなクラスをprivate継承して使うのも良い
class Uncopyable {
protected:
    Uncopyable() {}
    ~Uncopyable() {}
private:
    Uncopyable(const Uncopyable&);
    Uncopyable& operator=(const Uncopyable&);
}

7項 ポリモーフィズムのための基底クラスには仮想デストラクタを宣言しよう

  • 基底クラスは仮想デストラクタにすること
    • 派生クラスのポインタをdeleteする時にメモリリークになってしまう
  • 継承しないクラスは仮想デストラクタにしないこと
    • メモリの無駄遣いになってしまう

8項 デストラクタから例外を投げないようにしよう

  • デストラクタで例外を投げる関数を呼び出す場合、例外処理をする
  • もしくは、その関数を利用者側が呼ぶように別関数にして、そちらで例外処理をしてもらう

9項 コンストラクタやデストラクタ内では決して仮想関数を呼び出さないようにしよう

  • 関数の実体がない状態で呼ぶ可能性があるため

10項 代入演算子は*thisへの参照を戻すようにしよう

  • 「operator=」のようなコピー代入演算子は、「*this」を返すこと
    • x = y = z = 10; のようにつなげて使えるようにするため

11項 operator=の実装では、自己代入に備えよう

  • 自己代入とは、a = a; のこと。ポインタや配列で自己代入になる可能性はある
  • 自己代入になっても安全に動くように実装すること

12項 コピーするときは、オブジェクトの全体をコピーしよう

  • コピー関数(コピーコンストラクタ、コピー代入演算子)では、基底クラス含むメンバ変数すべてを忘れずにコピーすること

3章 リソース管理

13項 リソース管理にオブジェクトを使おう

  • newからdeleteまでに間があると、制御や例外でdeleteが呼ばれない可能性がある
  • newしたオブジェクトをデストラクタで確実に解放するスマートポインタにすぐ管理すると良い(RAIIオブジェクトと呼ぶ)
  • スマートポインタは、auto_ptrより、shared_ptrがより良い(参照カウンタがあるためコピーにも安全)

14項 リソース管理クラスのコピーの振る舞いはよく考えて決めよう

  • new、deleteではなく、ロック、アンロックのような場合、RAIIクラスを自作することになるが、この場合はコピー対応が必要となる
  • コピー対応は、コピー関数をprivateにして禁止にするか、リソースへの参照を数える、その他、で対応する

15項 リソース管理クラスには、リソースそのものへのアクセス方法を付けよう

  • RAIIクラスは、生ポインタを返す関数を提供しておくべき
  • 生ポインタは、明示的、暗黙的に返す方法はあるが、明示的な方が安全

16項 対応するnewとdeleteは同じ形のものを使おう

  • new int[100]は、delete[]を呼ぶようにすること
  • typedefなどで配列が見えない場合もあるので注意

17項 newで生成したオブジェクトをスマートポインタに渡すのは、独立したステートメントで行うようにしよう

  • スマートポインタに渡す時は、独立した1行で行うこと
    • 関数の引数渡しなどでスマートポインタ作成と他処理を1行でやると、他処理例外時にうまくスマートポインタが作られない

4章 デザインと宣言

18項 インタフェースは、正しく使うときには使いやすく、間違った使い方では使いにくいものにしよう

  • インタフェースは名前などに一貫性を持たせる
  • 新しい型を使えば、利用者が誤用できなくなる
  • newしたオブジェクトを返すのではなく、スマートポインタを返すこと

19項 クラスのデザインを型のデザインとして考えよう

  • オブジェクトの生成と破棄、newとdelete
  • オブジェクトの初期化や代入
  • 禁止すべき関数をprivateにしているか
  • 継承を想定しているか
  • 型変換を明示的にしてもらいたいか、暗黙的にやりたいか
    • もし暗黙的な型変換を嫌うなら、explicitをコンストラクタに付ける
  • メンバへのアクセスレベルは適切か

20項 値渡しよりconst参照渡しを使おう

  • オブジェクトの値渡しは、コンストラクタやデストラクタが無駄に呼ばれてしまう

21項 オブジェクトを戻すべき時に参照を戻そうとしないこと

  • ローカル変数の参照を戻すと、戻った時には破棄されたものへの参照が返ることになる

22項 データメンバはprivate宣言しよう

  • publicにするということは、変更ができなくなるということ

23項 メンバ関数より、メンバでもfrinedでもない関数を使おう

  • オブジェクトに関連している場合でも、普通の関数にした方がクラスがよりカプセル化される

24項 すべての引数に型変換が必要なら、メンバでない関数を宣言しよう

  • メンバ関数だと暗黙の型変換で呼べ出せないので、暗黙の型変換を利用したいなら普通の関数にする
  • 例えば、obj * 2、2 * objのどちらも使えるようにしたい場合

25項 例外を投げないswapを考えよう

  • 基本はstd::swap
  • 非効率的な場合(1つのポインタだけ交換すれば良い時など)は、自前のswapを作る(詳細略)

第5章 実装

26項 変数の定義は可能な限り先延ばししよう

  • 例外などにより、変数が使われない場合無駄になる
  • ループの中か外に定義するかは、コストによる

27項 キャストは最小限にしよう

  • 可能な限りキャストはやめる。特にdynamic_castはコストが大きい
  • キャストを使う場合、C++スタイルを使う
  • また、なるべく利用者側ではなく関数内に隠蔽するようにする

28項 オブジェクト内部のデータへの「ハンドル」を戻さないようにしよう

  • pricateのメンバ変数へのハンドル(ポインタ、参照など)を戻したらカプセル化を壊してしまう

29項 コードを例外安全なものにしよう

  • 関数の途中で例外が投げられても、リソース漏れを起こさずデータを有効に保つこと

30項 インラインをよく理解しよう

  • inlineは、小さくてそこそこ呼び出されるものに限定する
  • 具体的には、ゲッター・セッター、小さい関数あたり

31項 ファイル館のコンパイル依存性をなるべく小さくしよう

  • できるだけヘッダーファイルではなくcppファイルでincludeする
  • ハンドルクラス=pimplイディオム、インターフェースクラスを使うことで依存を減らすこともできる

第6章 継承とオブジェクト指向設計

32項 public継承はis-a関係を表すようにしよう

  • 派生クラスは基底クラスだが、基底クラスは派生クラスではない、という状態

33項 継承した名前を隠蔽しないようにしよう

  • 基底クラスと派生クラスに同じ名前があると隠蔽される(派生クラスでアクセスできなくなる)ので注意
  • 隠蔽された場合、using宣言をするとアクセスできるが、そもそも同じ名前にするべきではない

34項 インタフェースの継承と実装の継承の区別をしよう

  • 普通の継承、純粋仮想関数の継承、仮想関数の継承、非仮想関数の継承の違いを理解する

35項 仮想関数の代わりになるものを考えよう

  • NVIイディオム:public非仮想関数からprivate仮想関数を呼ぶ(仮想関数を非仮想関数でラップ)
  • 関数ポインタのメンバ変数
  • std::tr1::function:関数ポインタのようなもの
  • クラスの仮想関数を別クラスの仮想関数で置き換え(ストラテジーパターン)

36項 非仮想関数を派生クラスで再定義しないこと

  • 同名の関数呼び出しでも、場合のよって動作が基底クラスか派生クラスのものに変わってしまうため

37項 継承された関数のデフォルト引数値を再定義しない

  • デフォルト引数は基底クラス、動作は派生クラスになってしまうことがある
  • NVIイディオムを使って非仮想関数でデフォルト引数を付ければ良い

38項 コンポジションを使ってhas-a関係、is-implemented-in-terms-of関係を作ろう

  • コンポジションとは、クラスのメンバ変数に他クラスを保有すること(has-a関係)
  • public継承はis-a関係
  • is-implemented-in-terms-ofは実装関係で、他クラスのメンバ変数を使って実装をする(ラッパーに近い)

39項 private継承は賢く使おう

  • private継承は、is-implemented-in-terms-of関係
  • たいていはコンポジションに劣るが、基底クラスのprotectedにアクセスしたり、オーバーライドの必要があればこちらを選択する

40項 多重継承は賢く使おう

  • 多重継承は単一継承より複雑になる
  • 曖昧になる時は仮想継承を使う。ただ、仮想継承は遅かったりコストが高くなる

第7章 テンプレートとジェネリックプログラミング

41項 暗黙のインタフェースとコンパイル時ポリモーフィズムを理解しよう

  • 暗黙のインタフェース:関数のシグネチャではなく、T型の変数が呼ぶ関数などで決まる
  • コンパイル時ポリモーフィズム:仮想関数を通して実行時に実現

42項 typenameの2つの意味を理解しよう

  • T型はtypename、classどちらでも良いが、ネストされたT型指定時はtypenameが必要

43項 テンプレート化された基底クラス内の名前へのアクセス方法を知っておこう

  • アクセス方法は、this->を付ける、using宣言、基底クラスを示す修飾、の3つ

44項 パラメータに依存しないコードはテンプレートから外に出そう

  • テンプレートは複数のクラスや関数を生成するため、コードの膨張を招く
  • コードの膨張は、メンバ変数への置き換えや、実装の共有などで軽減できる

45項 「すべての五感型」を受け取るためにメンバ関数テンプレートを使おう

  • テンプレート用のコピーコンストラクタ、コピー代入演算子を宣言しても、通常のコピーコンストラクタとコピー代入演算子も宣言する必要がある

第8章 newとdeleteのカスタマイズ

49項 new-handlerを理解しよう

  • set_new_handlerによって、メモリ確保失敗時に呼ばれるエラー処理関数を指定できる