ホームに戻る
 Effective C++ 覚え書き

0、はじめに

Effective C++ 第3版 はとても良い本です。
この本の良い点を簡単に3つにまとめると、

1、C++ の落とし穴がどこにあるかがわかる
2、効率的なコードをどのように書くべきかがわかる
3、普段は気にしなかった内部の仕様について学べる

特に、3は重要です。
コードをコンパイラがどのように解釈し、
どのような実行バイナリを作成するかは意識する必要があるし、
そのことについて書かれた本は少ないと思うのです。

この文章は本を読んだ自分用の覚え書きです。
原書の文章を自分の解釈で大幅に書き換えており、
サンプルコードですら大きく書き換えられています。
原書に対して間違った解釈をした部分があるかもしれません。
最終的には原書で確認をするのが良いと思います。

コンパイラに bcc32 を使用します。
ただし str::tr1::shared_ptr は使えないようです。
よって、boost を使うことにします。

導入は bcc32.cfg を以下のようにする。

-I"c:\Borland\Bcc55\include;c:\Borland\Bcc55\boost_1_51_0"
-L"c:\Borland\Bcc55\lib"

使うときは以下をインクルードします。

#include <boost\shared_ptr.hpp>

また、テンプレートの項以降は bcc32 は使用しません。
テンプレートに関しては bcc32 が何でも許可をしてしまい、
本の内容通りのエラーを出さないためです。
テンプレートの項以降は gcc を使うことにします。

1項、C++は4つに分けられる

すなわち、
Cの部分、オブジェクト指向、テンプレート、STL
の4つ。
1つのものと考えると混乱のもとになる。

2項、#define は使うな。マクロ的なものはこう定義しろ!

#define のマクロはシンボルとして登録されない。
よって、コンパイラがエラーをだしてもシンボル名を表示してくれない。
シンボル名を有効にするために #define を使わず const を用いる。

const int num = 0;

ただし、文字列について次のような定義は推奨しません。

const char* const name = "John";

次のようにすると const が1つで済みます。

const std::string name("John");

また、クラス内に const を書く場合に注意があります。
定数が実際に1つしか存在しないように static を用います。
宣言はヘッダ内に記載します。

class A{
  private:
    static const int A_Num;
};

定義はソース内に記載します。

const int A::A_Num = 1;

また、enum ハックという便利な方法があります。
enum ハックを使用すると以下のような使用が可能です。

class A{
  private:
    enum{A_Num = 10};
    int list[A_Num];
};

ただし、enum ハックは int 値しか使用できません。

マクロ関数はどのように定義するか?
テンプレートと inline を利用して以下のように書けます。

template<typename T>
inline void call_f_max(const T& a, const T& b){
  f(a > b ? a: b);
}

3項、できるだけ const を使おう。const はこう使え!

const は変更不可という制約を与えます。
例えば次の int 値は不変です。

const int a = 1; // 初期値がないとエラーです。

ポインタについて次の定義を覚えます。
ポインタの場合 const をつける位置で意味が違います。

const char* p = "abc"; ポインタが指すデータが const
char* const p = "abc"; ポインタが const

実際 const をつけてもつけなくても動作は同じである場合が多いです。
ただし、const をつけることでコンパイラがミスを発見します。
もし、不変で利用できる値であるならできるだけ const をつけます。

またクラスの const なオブジェクトというものがあります。
このとき const なメンバ関数の定義が重要です。
const なオブジェクトは const なメンバ変数を使用します。

#include <iostream>

class A{
  int n;
public:
  A(int n = 0):n(n){};
  int &get(){return n;}
  const int &get() const {return n;}
};

int main(){
  A a1(3);
  const A a2(5);

  a1.get() = 4;
  //a2.get() = 4;  // エラー

  std::cout << a1.get();

  return 0;
}

2つ get が宣言されています。
const でないオブジェクトは const でない get を呼びます。
const なオブジェクトは const の get を呼びます。
get が片方しか宣言されていない場合はその get が呼ばれますが、
const なオブジェクトから 非const な get を呼ぶと警告が出ます。
エラーにはなりません。
この場合には const な get を定義するべきです。

const なオブジェクトはメンバ変数のビットレベルの不変を保障します。
よって上の例では n の値を書き換えようとするのでエラーになります。
ただし、メンバがポインタであった場合は注意が要ります。
const なクラスはポインタ自体のビットレベルの不変を保障します。
しかし、ポインタが指すデータの不変までは保障しません。

また、逆にビットレベルの保護が邪魔になる場合があります。
const なクラスを作りたいけれど内部で可変なフラグなどを使いたい場合です。
このときは mutable をつけることで変更可能になります。
例えば以下の例は mutable のついた可変なメンバ変数を持ちます。
ただし、const なクラスとして意味合い的には不変な結果を返します。

#include <iostream>

class A{
  int n;
  mutable int ans;
  mutable int flag;
public:
  A(int n = 0):n(n),flag(0){};
  const int &get() const {
    if(flag == 0){
      int x = 0;
      // 時間のかかる計算
      ans = x;
      flag = 1;
    }
    return ans;
  }
};

int main(){
  const A a1;

  std::cout << a1.get();

  return 0;
}

最後に、const ありと const なしで同じ処理を書く場合、
同じコードを2つ書くと片方を修正した場合に片方を修正し忘れる問題が発生します。
この場合には以下のように書くことができます。


#include <iostream>

class A{
  int n;
public:
  A(int n = 0):n(n){};
  const int &f() const {return n;}  // 変更するときはこちらだけでOK
  int &f(){
    return const_cast<int&>(static_cast<const A&>(*this).f());
  }
};

int main(){
  A a1;
  const A a2;

  std::cout << a1.f();
  std::cout << a2.f();

  return 0;
}

const のないほうから無理やり const をつけて f() を呼び出す。
返ってきた const つきの値から無理やり const を外す。
見苦しいかもしれないがこうするより仕方がない。
また、この逆はできないので試みようとしないこと。

4項、初期化の仕組みを知ろう

C++では、

int x;

のとき x の値は不明です。
適当な値で初期化されないので注意しましょう。
このような不安定な状態は適切ではないので必ず初期化しましょう。

int x = 1;

と書くときに初期化になります。

int x;

x = 1;

は初期化ではなく代入なので不適切です。注意しましょう。

クラスの場合はできるだけ初期化します。
初期化はコンストラクタ内で以下のように書きます。
このとき初期化の順番は num1 -> num2 です。
これはメンバ変数が定義された順番になります。

class A{
  int num1; // ここの順番は大事
  int num2;
public:
  A():num1(0), num2(0){};
};

以下は代入であり好ましくありません。

class A{
  int num1;
  int num2;
public:
  A(){
    this->num1 = 0;
    this->num2 = 0;
  }
}

また、トラブルを起こす重大なケースがあります。

まず、クラスをヘッダで定義します。
ソースにグローバルでオブジェクトを作成します。
これを外部のソースから extern で参照することはありそうです。
このときに初期化の順番が問題になります。
extern が参照するオブジェクが初期化されているかは不明なのです。

この問題を回避するために以下のように書きます。
オブジェクトは static で用意し、必ず参照させます。
こうすることで確実に初期化されていることが保障されます。

// ヘッダ
class A{
  public:
    void f(){};
};

// ソース
A &a(){
  static A a;
  return a;
}

5項、クラスの定義に書かなくても自動生成されるもの

書かなくても定義されるのは
コンストラクタ、コピーコンストラクタ、デストラクタ、コピー代入演算子
の4つ。

class A{};

と書いたときには自動的に次のように書いたのと同じになる。

class A{
  public:
    A(){
      // なんらかの処理
    };
    A(const A& a){
      // なんらかの処理
    };
    ~A(){
      // なんらかの処理
    };
    A& operator=(const A& a){
      // なんらかの処理
    };
};

どういった処理を生成するかは以下の通り。

自動生成されるコンストラクタとデストラクタは、
基底クラスや static でないクラスの
コンストラクタとデストラクタを呼び出す。
基底クラスのデストラクタが仮想であれば
自動生成されるデストラクタも仮想になる。

自動生成されるコピーコンストラクタとコピー代入演算子は、
コピー元の static でないデータメンバをコピーする。
コピーコンストラクタの場合は
コピーコンストラクタを持つばあいはコピーコンストラクタでコピーされる
コピーコンストラクタを持たない場合はビットレベルのデータコピーになる

また、コピー代入演算子は自動生成を拒否する場合がある。
参照や const 値がデータメンバにある場合に拒否される。
また、基底クラスのコピー代入演算子が private の場合に
派生クラスでのコピー代入演算子の自動生成を拒否する。

6項、コピーコンストラクタとコピー代入演算子を禁止する方法

オブジェクトが概念として複製されるべきもので無い場合。
コピーコンストラクタやコピー代入演算子の生成を拒否したい。
このとき次のようにする。

class A{
  public:

  private:
    A(const A&);
    A& operator=(const A&);
};

private でコピーコンストラクタとコピー代入演算子を宣言する。
宣言のみで中身の定義を書いてはいけない。

7項、デストラクタを仮想で書くとき、書かないとき

派生クラスを指す基底クラスのポインタを作成する場合。
仮想デストラクタを持たない基底クラスのポインタを delete すると
派生クラスのデストラクタが呼ばれない。

よって、基底クラスのデストラクタは仮想にする必要がある。

ただしデストラクタはすべて仮想でなくても良い。
仮想関数は派生先にアクセスするためのポインタテーブルを作成する。
この場合には無駄にメモリを使用してしまう。
よって、何でも仮想にする必要は無い。

オマケ:仮想関数を持たないクラスを抽象クラスにする方法

仮想デストラクタを純粋仮想デストラクタにする。
注意として純粋仮想デストラクタの定義を必ず書くこと。
派生クラスのデストラクタが基底クラスのデストラクタを呼ぶからです。

class A{
  public:
    virtual ~A() = 0;
};

A::~A(){};

8項、デストラクタから例外を呼ぶな

アクティブな例外は複数あってはならない。
デストラクタはすべてのメモリを解放する必要があるので、
1つの処理が例外を投げても次のメモリを解放しようとする。
この処理でも例外を投げるとすると処理は予想のできないものになる。

もしデストラクタ内で例外を投げるなら2通りの解決法がある。

その1:例外を投げたら catch して std::abort() する。
その2:例外を投げたら catch してそのまま続ける。

その1の場合はプログラムの実行を止めても良い場合に使える。
その2は例外があってもその先の処理につじつまがあう場合に使える。

いちばん良いのはデストラクタから例外を投げる処理を書かないのが良い。
例外を投げて問題のある処理はできるだけ関数内で行う。

9項、コンストラクタ、デストラクタ内で仮想関数を呼ばない

class A{
  public:
    A(){
      f();
    };
    virtual void f() const = 0;
};

class B : public A{
  public:
    virtual void f() const {};
};

B b;

上の処理は仮想関数を持つので基底クラスの A が
コンストラクタ内で B の仮想関数 f を呼びそうである。
ただし、基底クラスの A は A の仮想関数 f を呼ぼうとする。

なぜならコンストラクタは A → B の順番に実行されるので、
A のコンストラクタ内では B は無いものとして扱われる。
よって、仮想関数であっても A の仮想関数が呼ばれる。

これはデストラクタについても同じことが言える。
B → A の順にデストラクタが呼ばれるので、
A のデストラクタは A の仮想関数を呼ぼうとする。

10項、代入演算子は *this への参照を返すようにする

class A{
  int n;
  public:
    A& operator=(const A& a){
      this->n = a.n;
      return *this;
    }
};

こうすることで、

a1 = a2 = a3;

といった代入が可能になる。

このような代入は必要ないと思うかもしれないが、
C++は当然このような代入ができるように作られているので、
このような仕組みを作るようにしたほうが良い。
もちろん次のような記述にも対応するべきである。

a1 *= a2 += a3;

11項、operator は自己代入に備えよう

例えば x1 に x2 をコピーする場合。
まず x1 の中身を破棄する。
次に x1 に x2 の内容をコピーコンストラクタでコピーする。
この処理は次のように書ける。

// ダメな例
A& A::operator=(const A& a){
  delete pdata;
  pdata = new Data(*a.pdata);
  return *this;
};

このとき x1 = x1 と書くと予想のできない結果になります。
x1 の中身を破棄した時点で x1 を x1 にコピーできなくなります。
よって、次のように書くと安全です。

A& A::operator=(const A& a){
  Data *temp = pdata;
  pdata = new Data(*a.pdata);
  delete temp;
  return *this;
};

自分自身をコピーする処理に備えましょう。

12項、コピーコンストラクタ、コピー代入演算子はすべてをコピーすること

コピーコンストラクタ、コピー代入演算子は、
自動生成に任せないのであればすべてのデータをコピーする必要があります。
特に、基底クラスのコピーは忘れることが多いです。
基底クラスのコピーは次のように行います。

class A{
  int na;
  public:
    A(const A& a){};
    A& operator=(const A& a){
      na = a.na;
      return *this;
    };
};

class B : public A{
  int nb;
  public:
    B(const B& b):A(b){};
    B& operator=(const B& b){
      A::operator=(b);
      nb = b.nb;
      return *this;
    }
};

13項、メモリ管理をスマートポインタで行う方法

メモリの確保は new で行われ delete で破棄されます。
ただし、new は呼ばれても記述のミスで delete が呼ばれないことがあります。
確保したメモリが開放されないことをメモリリークと呼びます。
確保したメモリを delete せずに開放する仕組みがあります。
これを「スマートポインタ」といいます。

std::auto_ptr<A> p(new A());

スマートポインタはオブジェクトを作成します。
よって破棄されるときに必ずデストラクタを呼びます。
スマートポインタはデストラクタ内でスマートポインタが指すオブジェクトのデストラクタを呼びます。
スマートポインタは自動的にメモリを開放してくれる、ポインタのオブジェクトバージョンと言えます。
また、スマートポインタはコピーすると内容はコピー先に移り、
コピー元は自動的に NULL になります。
よって、常に1つのメモリは1つのスマートポインタで管理されます。

1つのメモリを複数のスマートポインタで管理できます。
次のようにします。

#include <memory> 

boost::shared_ptr<A> p1(new A());
boost::shared_ptr<A> p2(p1);

複数のスマートポインタの最後の1つが消えたときに、
メモリの内容が破棄されます。

注意として配列に対してスマートポインタは使ってはいけません。
この場合には配列を使わずに vector を使うようにしましょう。

14項、複数アクセスするリソース管理の場合はコピーに気をつけろ

ファイルのオープン、クローズを管理するクラスを作ろうとする。
ファイルに関する用件は以下のようである。

class A{
  public:
    A(){};
    void open(){
      std::cout << "file open" << std::endl;
    }
    void close(){
      std::cout << "file close" << std::endl;
    }
};

void open(A *a){a->open();}
void close(A *a){a->close();}

static A a;

このとき A を管理するクラス B を作ってみる。

class B{
  A *a;
  public:
    B(A *a):a(a){
      open(a);
    }
    ~B(){
      close(a);
    }
};

この B はブロックの中で、

B b(&a);

と書けばファイルオープンする。
そして、ブロックを抜けたときに自動的にクローズを呼んでくれる。

ただし、次のようなとき問題が起こる。

B b1(&a);
B b2(b1);

このとき b1 と b2 は両方デストラクタを呼ぶ。
1つのオープンに対して2つのクローズを行い管理として正しくない。

よって、クラス B は次のように書くと良い。
スマートポインタ shared_ptr を利用します。

class B{
  boost::shared_ptr<A> pa;
  public:
    B(A *a):pa(a, close){
      open(pa.get());
    }
};

この場合はデストラクタで pa がデリートされるので、
close の処理をスマートポインタのデリータで行います。
この方法はリソース管理クラスを作る際のアイデアとして役立つ。

15項、リソース管理のクラスはリソース自体を返すようにしよう

リソースを管理するクラスの目的はリソースの管理であってカプセル化では無い。
スマートポインタでもリソースを取り出す get() を持っていますし、
-> 演算子や * 演算子を通してのリソースへのアクセスを許可している。
リソースを管理するクラスも同じようにするべきです。
次の例ではクラス A を管理する B から A のポインタを取り出す get() を実装しています。

class A{
  public:
    void print(){std::cout << "A" << std::endl;}
};

void f(A *a){a->print();}

class B{
  A *a;
  public:
    B():a(NULL){
      a = new A();
    };
    ~B(){delete a;}
    A *get() const {return a;}
  private:
    B(const B&);
    B& operator=(const B&);
};

このようにするとクラスAを引数に持つ関数 f をBのオブジェクトから呼べます。

B b;

f(b.get());

16項、変数の delete と配列の delete は違う

変数は delete 配列は delete [] を使う。
変数に delete [] しても配列に delete してもコンパイルエラーは出ない。
ただし、これはメモリ管理としては大きな間違いになるので注意する。
以下は正しい使用例。

int *n = new int(3);
int *a = new int[5];

a[1] = 4;

std::cout << *n << std::endl;
std::cout << a[1] << std::endl;

delete n;
delete [] a;

17項、new してスマートポインタに入れるときの注意

次のような関数 f1 内でスマートポインタを作成する。

f1(std::auto_ptr<A>(new A), f2());

このとき思いがけない動作をすることがある。
new A されてからスマートポインタに渡される順序は守られるが、
間に関数 f2 の処理が行われる可能性がある。
すると関数 f2 が例外を投げた場合に、
A のオブジェクトはスマートポインタに渡らないので解放されない。
対応は簡単である。
次のようにして

std::auto_ptr<A> pa(new A);

それから関数 f1(pa, f2()) とすれば良い。

スマートポインタへの代入は単独行で行うようにする。

18項、インターフェイスの使いかた

次のようなクラス Date を作成します。

class Date{
  public:
    Date(int year, int month, int day);  // 宣言のみ
};

このとき日付を設定するとき

Date d(2000, 4, 7);

とすれば良いが、もっと良い方法がある。

class Date{
  public:
    Date(const Year& y, const Month& m, const Day& d);
};

このとき日付を設定するとき Year、Month、Day というクラスを用意し

Date d(Year(2000), Month(4), Day(7));

とできる。

この工夫によって年、月、日の入れ違いのミスを予防できる。
また、その引数が何を意味するかがわかりやすくなる。

さらに改良して月のクラスを以下のようにする、

class Month{
  public:
    static Month Jan(){return Month(1);}
    static Month Feb(){return Month(2);}
    static Month Mar(){return Month(3);}
    static Month Apr(){return Month(4);}
    static Month May(){return Month(5);}
    static Month Jun(){return Month(6);}
    static Month Jul(){return Month(7);}
    static Month Aug(){return Month(8);}
    static Month Sep(){return Month(9);}
    static Month Oct(){return Month(10);}
    static Month Nov(){return Month(11);}
    static Month Dec(){return Month(12);}
  private:
    explicit Month(int m){};
};

このとき日付を設定するとき

Date d(Year(2000), Month::Apr(), Day(7));

とできる。

こうすると不正な月を入力できないようになる。

19項、クラスのデザインを考えよう

クラスをデザインするときに以下の項目をチェックしよう

・生成と破棄は適切か
・初期化と代入はどのように動作し、どう違うか
・コピーコンストラクタは何を渡すか
・オブジェクトが持つ有効な値は何か
・派生クラスを適切に作ることができるか
・型変換を行うか
・演算子や関数は何か
・private で宣言すべきものは何か
・外からのメンバへのアクセス権はどうか
・クラスの実装の意味はなにか
・テンプレートを定義するか
・本当にクラスで作るべきか

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

例えば次のような値渡しの場合、

class A{
  public:
    void print(){std::cout << "A" << std::endl;}
};

class B{
  public:
    void print(A a){a.print();}
};

クラスBのメンバ関数 print はクラスAのオブジェクトを引数にとります。
このとき値渡しをするとクラスAのオブジェクトが新たに作成されます。
このときコピーコンストラクタ、デストラクタが呼ばれるでしょう。
ただし、クラスAのオブジェクトを使うだけならこの作業は省略できます。

この省略が const での参照渡しで行えます。

class A{
  public:
    void print(){std::cout << "A" << std::endl;}
};

class B{
  public:
    void print(const A& a){a.print();}
};

参照渡しをすることでコンストラクタ、デストラクタは呼ばれません。
ただし、注意するのは const をつけることです。
const をつけないと print 内で値が書き換えられる危険性があります。

また const の参照渡しはスライス問題を回避します。
例えば次のように書いたとき、

class A{
  public:
    virtual void print() const {std::cout << "A" << std::endl;}
};

class B : public A{
  public:
    virtual void print() const {std::cout << "B" << std::endl;}
};

class C{
  public:
    void print(A a){a.print();}
};

次の処理は何を表示するでしょうか?

B b;
C c;

c.print(b);

答えは

A

です。

仮想関数を呼んでいるにも関わらず B ではなく A を表示します。
これはいったんクラスAに置き換えられてしまうからおこります。
この場合も const の参照渡しを使えば期待する仮想関数の動作をします。

以上の理由により const 参照渡しを使うべきである。

21項、参照を戻すのが不適切な場合

const の参照渡しは使うべきときに使うべきであるが、
次のような場合は参照返しはもっての他である。

class A{};

class B{
  public:
    const A& f(){
      A a;
      return a;
    }
};

クラスAのオブジェクトはメンバ関数終了時に消えてしまう。

同じく次のようにするのもいけない。

class B{
  public:
    const A& f(){
      A *a = new A();
      return *a;
    }
};

動的にオブジェクトを作成しているので良いように思えるが、
このオブジェクトがどのように消されるかが不定である。

次の例もダメな例である。
クラスBのオブジェクトを複数作ったときに誤動作する可能性がある。

class B{
  public:
    const A& f(){
      static A a;
      return a;
    }
};

22項、データメンバは private にしよう

データメンバは private で管理する。
データメンバにアクセスするメンバ関数を public にする。
こうすることで値の代入のチェックを入れることができる。
もしデータメンバを public にしてしまうと、
データメンバがコードにいたるところに記述され、
データメンバの仕様に変更を加えたいときにコード中を変更する必要がでてくる。
同じ理由でデータメンバを protected にするのもお勧めできない。
派生クラスから直接アクセスするコードを書いてしまった場合、
基底クラスの protected なデータメンバの仕様を変更すると、
このデータメンバを使う派生クラスのメンバ関数をすべて書き換える必要がでてくる。

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

次のクラスAで f1、 f2、 f3 を続けて呼びたいとする。

class A{
  public:
    void f1(){};
    void f2(){};
    void f3(){};
};

このとき次のようなメンバ関数 f_all を作れる。

class A{
  public:
    void f1(){};
    void f2(){};
    void f3(){};
    void f_all(){
      f1();
      f2();
      f3();
    }
};

ただしこれは適当でない。
なぜなら f_all が private なデータメンバにアクセスできる。
f_all を追加することで private なデータメンバにアクセスさせるメリットは無い。
よって、次のように書くべきである。

class A{
  public:
    void f1(){};
    void f2(){};
    void f3(){};
};

void f_all(A &a){
  a.f1();
  a.f2();
  a.f3();
}

このように書けば f_all は private なデータメンバにアクセスできない。

24項、2 * A に対応しよう

次のようなクラスAを作成します。

class A{
  int n;
  public:
    A(int n = 0):n(n){};
    const A operator*(const A& a)const{
      return n * a.n;
    }
};

このとき次のような使い方ができます。

A a1(3);
A a2(5);
A a3(a1 * a2);

次のようにも使えます。

A a1(3);
A a2(a1 * 2);

ただし次のようには使えません。

A a1(3);
A a2(2 * a1);

このように書いても動作する必要があります。
この問題を解決するために次のように書きます。

class A{
  int n;
  public:
    A(int n = 0):n(n){};
    int get() const {return n;}
};

const A operator*(const A& a1, const A& a2){
  return A(a1.get() * a2.get());
}

25項、swap を書くときはここまでやれ

std には swap が準備されている。

#include <algorithm>

int a = 1, b = 2;

std::swap(a, b);

と書くと値を交換する。
内部の実装は単純に次のような方法である。

int temp = a;
a = b;
b = temp;

swap はテンプレートで実装されておりクラスにも対応する。

A a1(3);
A a2(5);

std::swap(a1, a2);

クラスAについてコピーコンストラクタとコピー代入演算子が適切に設定されているならこの交換が成功する。

しかし、次のようなクラスAについてはどうだろうか?

class A{
  int *p;
  public:
    A():p(NULL){
      p = new int[200];
    }
    ~A(){
       delete [] p;
    }
};

このときクラスのオブジェクトを丸ごとコピーする必要はない。
ただ、p のアドレスを入れ替えるだけで良い。

よって、次のような仕組みを考える。

class A{
  int *p;
    public:
    A():p(NULL){
      p = new int[200];
    }
    ~A(){
       delete [] p;
    }
    void swap(A& a){
      using std::swap;

      swap(p, a.p);
    }
};

namespace std{
  template<>
  void swap<A>(A& a1, A& a2){
    a1.swap(a2);
  }
}

このようにすると std の処理をクラスAについてだけ特化させることができる。
すなわち swap() の引数がクラスAのときだけ通常の swap と違う処理を行うということができる。

ただし、まだ不完全である。
クラスAがテンプレートで書かれている場合にこの書き方は通らない。
このときは次のように書く。

namespace AStuff{
  template<typename T>
  class A{
    T *p;
      public:
      A():p(NULL){
        p = new T[200];
      }
      ~A(){
         delete [] p;
      }
      void swap(A& a){
        using std::swap;

        swap(p, a.p);  // std::swap と書かない
      }
  };

  template<typename T>
  void swap(A<T>& a1, A<T>& a2){
    a1.swap(a2);
  }
}

このとき

std::swap(a1, a2);

では AStuff という名前空間の swap が呼ばれない。

using namespace AStuff;

swap(a1, a2);

とするのが適切である。

なお、メンバ関数の swap は例外を返さないようにすること。

26項、変数はできるだけ後で定義しよう

たとえばクラスAとクラスAの結果を用いるクラスBがあったとする。

このときつぎのようにできる。

void f(){
  A a;
  B b;

  a.f();

  b.set(a);

  b.f(); // 内部で a を用いる
}

これは次のように書くと良い。

void f(){
  A a;

  a.f();

  B b;

  b.set(a);

  b.f();
}

a.f() が失敗したら処理を中断して戻すというふうにした場合に
余計なオブジェクトbを作成しなくても良い。

もっというと次のようにしたほうが良い。

void f(){
  A a;

  a.f();

  B b(a);

  b.f();
}

クラスBはクラスAのオブジェクトを使うに決まっているのだから
クラスAのオブジェクトがなければ生成できないように設計をする。

話は変わって、次のような実装はどちらのほうが良いのか?

その1:

A a;

for(int i = 0; i < n; i++){
  a.set(i);
  // ある処理
}

その2:

for(int i = 0; i < n; i++){
  A a(i);
  // ある処理
}

正解は「その2」です。
この場合、オブジェクトaはできるだけ後で定義されており、
スコープが限定されるので管理がやりやすくなります。

ただし、「1回の代入」が「1回の作成と破棄」より効率が良く、
その効率が重要になる場合は「その1」を選択する方法もある。

27項、キャストの書き方

古いタイプのキャストは次のように書く。

double b = 1.2;

int a = (double)b;

もしくは、

int a = double(b);

と書ける。

ただし、この古いタイプのキャストはC++では使うべきでは無い。
このキャストは人間が見て見つけにくく文字列検索機能でも見つけにくい。

普通は次のように行う。

double b = 1.2;

int a = static_cast<int>(b);

このキャストによって、const を外す以外のキャストが可能である。

const を外す場合には次のようにする

const int b = 1;

int a = const_cast<int>(b);

reinterpret_cast はポインタのキャストを行う場合に使用する。
例えば、ポインタのアドレスを int 値にすることができる。
ただし、この使用は低レベルな実用性しか無い。

char *b = NULL;

int n = reinterpret_cast<int>(b);

dynamic_cast はダウンキャストに使用します。

ダウンキャストの前にまずアップキャストを説明します。
クラスBはクラスAを継承しているとします。

A *a;
B* b = new B();
a = b; // アップキャスト

アップキャストは実用的であり使用してもなんの問題もありません。

ダウンキャストは次のようなものです。

B b;
A *pa = &b;
B* pb = dynamic_cast<B *>(pa); // ダウンキャスト

if(pb == NULL){
  // ダウンキャスト失敗
}

クラスBがクラスAの継承で無い場合など失敗することがあります。
また、参照をダウンキャストすると例外を投げることがあります。

ダウンキャストは見てわかるとおり大きなものから小さいものを指す方法なので、
とても危険な操作であることがわかります。

キャストはあらゆる誤動作の元になるのでできるだけ使用を避けます。
どうしてもキャストを使う場合は関数の中に隠蔽するなどの方法を使い、
コードの中に頻繁にキャストが登場するような状態を避けましょう。

28項、データメンバを参照やポインタで返さない

次のようなクラスの定義には問題がある。

class A{
  int n;
  public:
    A(int n = 0):n(n){};
    void set(int n){this->n = n;}
};

class B{
  A *a;
  public:
  B():a(NULL){
    a = new A(0);
  };
  ~B(){delete a;}
  A &getA() const {return *a;}
};

このときの問題点は次のようなコードが書ける点である。

const B b;

b.getA().set(3);

オブジェクトbは const であるにもかかわらず値が変更できてしまっている。
なぜならクラスBのデータメンバはポインタであるので、
ポインタのアドレスの書き換えは保護してもアドレス先の変更は保障しないのである。

これを解決するコードは単純である。

class A{
  int n;
  public:
  A(int n = 0):n(n){};
  void set(int n){this->n = n;}
};

class B{
  A *a;
  public:
  B():a(NULL){
    a = new A(0);
  };
  ~B(){delete a;}
  const A &getA() const {return *a;}
};

データメンバはポインタや参照を直接返さない。
operator[] で参照を返すときにも注意がいる。
もし返すのであれば const をつけること。
これでデータメンバ自体が保護されることになる。

ただし、string や vector は operator[] で const でない参照を返している。
これはそういうデザインなのであって、
string や vector の本体が消えればその参照も消えることに注意する必要がある。

29項、例外への対策

例外で起こってはならない状態には2つある。

・生成したメモリが解放されない(メモリリーク)
・例外後の処理のつじつまがあわない

生成したメモリの解放忘れは13項でスマートポインタを使う方法を示した。
この項では例外が発生した場合にデータ構造につじつまが合わない場合を考える。

例えば、AとBという一連の処理を行いたい場合、
Aという処理は成功したがBという処理が失敗したとしよう。
このときAでメモリを確保していたとして、これはスマートポインタで解決できる。
ただし、Aのデータメンバに変更を加えていた場合、
それはそのままにしておいて良いかという問題がある。

問題がある場合は、
Bが失敗して正しい処理が実際に行われなかったけれども。
とりあえずAは成功していたのでカウントを1加算していたような場合である。

この問題の解決法は3つある。

その1:後からつじつまをあわせる方法
その2:無かったことにしてすべてを元に戻す方法
その3:例外が無い処理に書き換える

その1の場合はカウントは1加算されるがデータは無効なデータとして放置する場合。
この場合はデータが無効である場合を考慮してクラスをデザインする必要がある。

その2の場合はカウントが1加算されているので無かったことにしてカウントを元に戻す。

その3は新たなデータが確実に生成される工夫をする。

自由に選択できるのであれば「その3」を選んでおきたい。
ただし、その3はほとんどのケースにおいて可能で無い。

よって、次のように作成するのが良い。

まず、仮のデータ構造を用意しておき例外の起こる可能性のある処理をすべて行う。
すべてが成功したら本当のデータと仮のデータをすべて swap する。
この操作はわかりやすい発想で上の「その2」を実現している。

30項、インラインでコンストラクタを書いていいかどうかを考えよう

インライン化されるということはどういうことか真剣に考えてみる。
すべてインラインを使えば必ず効率が良くなるというのは幻想である。

その理由は、

インラインを行うと実行バイナリ内に同じ内容がところどころに埋め込まれることになる。
すると、実行バイナリは肥大化し実行バイナリがCPUのキャッシュに蓄えられる確率が減ることになる。
これはすなわちパフォーマンスの低下といえる。

よって、インラインは単純に値を入れるとか取り出すとかそういう用途に使うべきである。

注意すべき点をひとつあげておく。
例えば、以下のようなクラスCのコンストラクタをインライン化するとします。

class A{};

class B{
  A a1, a2;
};

class C : public B{
  public:
    C(){};
};

クラスCのコンストラクタの内部は {} と何も無いのでインライン化しても良さそうです。
しかし、実際には見えないところで以下のようなことをしています。
(以下はイメージのコードです。)

C::C{
  B::B();
  try{
    a1.A::A();
  }
  catch(...){
    B::~B();
    throw;
  }
  try{
    a2.A::A();
  }
  catch(...){
    a1.A::~A();
    B::~B();
    throw;
  }
}

これをインライン化しても良いでしょうか?
もしCのコンストラクタがコードの中で頻繁に呼ばれるのであれば、
面倒でも以下のように書くことに意味があるのではないでしょうか?

class C : public B{
  public:
    C();
};

C::C(){}

31項、開発効率を上げるために宣言と実装を分けろ

次のようなクラスAを a.hpp に用意するとします。

#include "b.hpp"

class A{
  B b;
  public:
    A(const B& b);
    B get() const;
};

このように書くとクラスAはヘッダ b.hpp に依存するようになります。
つまり、クラスBの内容を変更するとクラスAのサイズが変わるので、
クラスAのオブジェクトを作成するコードをコンパイルする必要があります。
さて、これはしかたがないことのように思えます。
いま大きなプロジェクトがありクラスAを複数のコードで使用するとします。
このときBの実装をたった1つ変えたとします。
このときほとんどすべてのコードでコンパイルが必要になります。
くりかえしテストを行う場合など大きく開発効率を下げることになります。

これを解消するために宣言のみを書き実装を書かない方法があります。
次のコードは宣言のみなのでエラーのように思えます。

class A;

A f1();
void f2(A a);

ただし、使用しないのであれば宣言をすること自体はエラーではありません。

この方法を使って宣言のみで実装を伴わないヘッダを書いてみます。
やり方は2つあります。
ハンドルクラスを使う方法とインターフェイスクラスを使う方法です。

ハンドルクラスの使い方。

// a.hpp

class A2;
class B;

class A{
  boost::shared_ptr<A2>pa2;
  public:
    A(const B& b);
    int f() const;
};

// a2.hpp

#include "b.hpp"

class A2{
  B b;
  public:
    A2(const B& b);
    int f() const;
};

// a.cpp

#include "a.hpp"
#include "a2.hpp"

A::A(const B& b):pa2(new A2(b)){}
int A::f(){
  return pa2->f();
}

以上のようにすれば a.hpp をインクルードし使うコードを書いても、
クラスBの実装に変更があった場合に再コンパイルされません。

インターフェースクラスの使い方。

// a.hpp

class B;

class A{
  public:
    virtual ~A();
    virtual int f() const = 0;
    static boost::shared_ptr<A>create(const B& b);
};

// a2.hpp

#include "a.hpp"
#include "b.hpp"

class A2 : public A{
  B b;
  public:
    A2(const B& b):b(b){};
    virtual ~A2(){};
    int f() const;
};

// a.cpp

#include "a.hpp"
#include "a2.hpp"
#include "b.cpp"

boost::shared_ptr<A> A::create(const B& b){
  return boost::shared_ptr<A>(new A2(b));
}

インターフェースクラスを使用する場合には次のようにします。

B b;
boost::shared_ptr<A> pa(A::create(b));

pa->f();

この2つの方法は開発効率が良いだけで実行効率を上げるものではありません。
よって、開発が終了したらこの仕組みは取り外してもいいのです。

32項、public 継承は理論的であれ

例えば、「人間」というクラスを作る。
「人間」を継承して「学生」というクラスを作る。
すべての学生は人間だから理論的に正しい。

では「学生」を継承して「人間」は作れるだろか?
これはすべての人間が学生ではないので不適切である。

public 継承はこのルールを必ず守る必要がある。

すなわち、

「派生クラスは基底クラスの条件をすべて満たす」

必要がある。

では、「鳥」というクラスに fly() というメンバ関数を用意するとします。
このときに「鳥」を継承して「ペンギン」を作るとします。
さて、「ペンギン」は飛ぶことができるでしょうか?
このときつじつまのあう継承は次のようなものではないでしょうか?

class Bird{};

class FlyingBird : public Bird{
  public:
    void fly();
};

class Penguin : publuc Bird{};

継承先は継承元のすべてのことができる必要があります。
概念の問題であっても、この法則は守らなければなりません。

33項、継承と名前のアクセス

クラスAを継承するクラスBがあったとき、
名前についての検索は次の順で行われます。

ローカルスコープ→現在のクラス→基底クラス→名前空間→グローバルスコープ

小さい範囲から大きな範囲へ向かって検索が行われます。

以下のクラスを使って実験をしてみます。

class A{
  public:
    void f1(){};
    void f3(){};
    void f3(int x){};
};

class B : public A{
  public:
    void f2(){};
    void f3(){};
};

このクラスで以下のように書くとどうなるでしょう?

B b;

b.f1();
b.f2();
b.f3();
b.f3(4);

正解は、

b.f3(4);

のみがエラーになる。

これは派生クラスで名前としての f3 が見つかっているので基底クラスを探さないことから起こります。

これは次のようにすれば解決できます。

class A{
  public:
    void f1(){};
    void f3(){};
    void f3(int x){};
};

class B : public A{
  public:
    using A::f3;

    void f2(){};
    void f3(){};
};

もしくは次のようにします。

class A{
  public:
    void f1(){};
    void f3(){};
    void f3(int x){};
};

class B : public A{
  public:
    void f2(){};
    void f3(){};
    void f3(int x){
      A::f3(x);
    }
};

この仕組みは32項の理論を実践するために必ず必要です。

34項、純粋仮想関数、仮想関数、非仮想関数の継承について

純粋仮想関数は継承先にインターフェイスのみを継承させる。

class A{
  public:
    virtual ~A(){};
    virtual void f1() = 0;
};

void A::f1(){}

class B : public A{
  public:
    virtual void f1(){};
};

純粋仮想関数にはなんと実装を書くことができる。
実装は以下のようにして呼ぶこともできる。

B *b = new B();

b->A::f1();

仮想関数は継承先にインターフェイスとディフォルトの実装を継承する。

class A{
  public:
    virtual ~A(){};
    virtual void f1(){};
};

class B : public A{
  public:
    virtual ~B(){};
    virtual void f1(){};
};

仮想関数は継承先で上書きしなければディフォルトの実装を与える。
注意する点は継承先で上書きしなければ勝手にディフォルトが実装される点である。
意図しないディフォルトの実装を拒否するために以下のように書ける。

class A{
  public:
    virtual ~A(){};
    virtual void f1() = 0;
};

void A::f1(){
  // ディフォルトの処理
}

class B : public A{
  public:
    virtual ~B(){};
    virtual void f1(){
      A::f1();
    }
};

もしくは次のように書いてもよい。

class A{
  public:
    virtual ~A(){};
    virtual void f1() = 0;
  protected:
    void default_f1();
};

void A::default_f1(){
  // ディフォルトの処理
}

class B : public A{
  public:
    virtual ~B(){};
    virtual void f1(){
      A::default_f1();
    }
};

非仮想関数は継承先にインターフェイスと変えてはいけない実装を継承する。
非仮想関数は継承先で変更してはいけないのです。

35項、継承先のメンバ関数の実装に多様性を付け加えるには?

基底クラスに仮想関数を書くとディフォルトの実装を用意することになる。
このやり方に特に問題は無い。
ただ、継承先でディフォルトに頼ってしまって、新たな実装を考えるのをサボることにはならないだろうか?
よって、次のようにも書けるという方法を紹介する。

class A{
    virtual void dof1(){
      // ディフォルトの実装
    }
  public:
    virtual ~A(){};
    void f1(){
      dof1();
    };
};

以上の方法は継承先での新たな実装を強制する。

別の方法を紹介します。
以下は、関数ポインタを与えて継承先で実装させる方法です。

class A;

void dof(const A&){
  // ディフォルト
}

class A{
  public:
    typedef void (*A1)(const A&);
    A(A1 a1 = dof):a1(a1){};
    void f() const{
      return a1(*this);
    }
  private:
    A1 a1;
};

class B : public A{
  public:
    B(A1 a1 = dof):A(a1){}
};

void f1(const A&){
  // f1 の処理
}

void f2(const A&){
  // f2 の処理
}

この方法では以下のように使用します。

B b1(f1);
B b2(f2);

b1.f();
b2.f();

関数オブジェクトを使う方法もあるがここには書かない。

36項、非仮想関数は再定義しない

class A{
  public:
    void f(){};
};

class B : public A{
  public:
    void f(){};
};

このような記述はエラーではありません。
しかし、2つの理由でデザインに矛盾が生じます。

・BがAの f() を必要とする時点でBはAの一種とは言えない
・f() は不変部分であり、可変であるなら仮想関数にするべきである

37項、ディフォルトの引数の継承に気をつけろ

class A{
  public:
    virtual void f(int n = 1){
      std::cout << "a" << n << std::endl;
    }
};

class B : public A{
  public:
    virtual void f(int n = 2){
      std::cout << "b" << n << std::endl;
    }
};

上のような定義のときに次の実行結果はどうなるだろうか?

A *a = new B();

a->f();

答えはなんと、

b1

である。

理由はディフォルトの引数はポインタのものが使われるためである。
よって、メンバ関数はBのものが呼ばれているのにディフォルト引数はAのものが使われる食い違いが起こる。

この問題への対応法は基底クラスでも拡張クラスでも同じディフォルトの引数を用いることである。
ただし、この方法は規定クラスを変更したときに拡張クラスの変更を忘れるという危険性を持っている。

よって、36項を参考に次のように書くのがよい。

class A{
    virtual void dof1(int n){
      // ディフォルトの実装
    }
  public:
    virtual ~A(){};
    void f1(int n = 1){
      dof1(n);
    };
};

38項、クラスのメンバにオブジェクトを定義する意味とは?

クラスのメンバにオブジェクトを配置する意味には2つある。
ひとつは「所有関係」であり、もうひとつは「実装関係」である。

「所有関係」とは、クラスBがクラスAを持っている関係。
クラスBがクラスAをデータとして使用する場合に使う。

class A{
  int n;
  public:
    void set(const int &n){
      this.n = n;
    }
    const int &get() const {
      return n;
    }
};

class B{
  A a;
  public:
    // a を使う処理
};

「実装関係」とは、クラスBがクラスAの実装を変更する。
これはクラスAをクラスBが拡張すれば実現できそうだ。
しかし、これは32項の理論に反する。
よって、これを拡張によって作成してはならない。

class A{
    public:
      void f(){};
};

class B{
  A a;
    public:
      void f(){
        // a を使って実装を変更
      }
};

39項、private 継承の使い方

まず private 継承は32項でいう理論とは別物になります。
private 継承は38項の「実装関係」を実現します。
非仮想関数を継承先で再定義しても問題ありません。

class A{
    public:
      virtual void f(){};
};

class B : private A{
    public:
      virtual void f(){
        // 実装を変更
      }
};

38項のやり方と違うところは2つあります。

private 継承は基底クラスの protected メンバにアクセスできます。
また、基底クラスの仮想関数をオーバーライドできます。

ただし、できれば38項のやり方を使うほうが望ましいのです。
例えば、上の例でクラスBを継承するクラスCを作るとします。
このときクラスCに f() を再定義させない35項のような書き方ができません。
また、31項のような宣言と実装の分離ができなくなります。

40項、多重継承の正しい使い方

多重継承がトラブルの元になる場合は次のようなケースです。

class A{};
class B : public A{};
class C : public A{};
class D : public B, public C{};

このときDからAにアクセスするとき、Bを経由したAなのかCを経由したAなのかわからなくなります。
BからアクセスしたAとCからアクセスしたAは別ものなのです。
DからAを呼ぶと区別ができないのでコンパイルエラーになります。

この解決は次のように行えます。

class A{};
class B : virtual public A{};
class C : virtual public A{};
class D : public B, public C{};

このように書くとBを経由してもCを経由してもAは同一のものと認識されます。
この場合はDからAを呼んでもコンパイルエラーになりません。
このときDのオブジェクトを作成するとDから直接Aのコンストラクタが呼ばれます。
BとCで競合するということはありません。

さて、ややこしいだけのように見える多重継承ですが有効な使い方もあります。

class A{
  public:
    virtual void f() const {};
};

class IB{
  public:
    virtual ~IB(){};
    virtual void f() const = 0;
};

class B : public IB, private A{
  public:
    B():A(){};
    virtual void f() const {};
};

この構造はインターフェイスからの継承と39項の private な継承を多重継承で定義しています。
この方法は多重継承であって、使い方も正当です。

41項、テンプレートは具体的にどういうものか?

次のようなテンプレートは正しいでしょうか?

template<typename T>
int f(T &w){
  if(w.f1() * w.f1()){
    return 1;
  }

  return 0;
}

これは正解です。
実際に使わないのであればコンパイルエラーはでません。
w.f1() がどういうものであるかはチェックされません。

ではこのテンプレートで使うクラスを以下のように定義してみます。
このクラスではメンバ関数 f1() は A の参照を返してます。
加えて * のオペレーターを定義しています。

class A{
  int n;
  public:
    A(int n = 0):n(n){};
    A &f1(){return *this;}
    bool operator*(const A &a){
      if(this->n == 1){
        return true;
      }
      else{
        return false;
      }
    }
};

この例は f1() があるので実際に使っても正確に動作します。
例えば次のように使ってもコンパイルエラーはでません。

A a(1);

f(a);

この例ではテンプレート関数内の

if(w.f1() * w.f1())

にて w.f1() の返す値の型チェックをしていないことがわかります。
w.f1() * w.f1() が bool 型でさえあればいいのです。

これらの例でテンプレートはどう定義されてどのように使えるかがわかります。

42項、テンプレートでの型名の使い方

次のようなコードは適切でしょうか?

template<typename T>
void f(T& a){
  T::nt n = 1;
  a.f(n);
}

class A{
  public:
  typedef int nt;
  void f(nt n){
    std::cout << n << std::endl;
  }
};

このとき

T::nt n = 1;

T::nt はもちろん型名です。
しかし、テンプレートは特に明示しなければこれを変数と解釈します。
よって、コンパイルエラーがでます。
これは次のように書けば解決できます。

template<typename T>
void f(T& a){
  typename T::nt n = 1;
  a.f(n);
}

例えば、次のように書くこともあります。

template<typename T>
void f(IterT& iter){
  typename std::iterator_trails<IterT>::value_type temp(*iter);
}

これではあまりに長すぎるので次のように書くと良いでしょう。

template<typename T>
void f(IterT& iter){
  typedef typename std::iterator_trails<IterT>::value_type value_type;
  value_type temp(*iter);
}

43項、テンプレート化された基底クラスへのアクセス

次のようなクラスBは fa() を呼べない。

template <typename T>
class A{
  public:
    void fa(){};
};

template <typename T>
class B : public A<T>{
  public:
    void fb(){
      fa();
    }
};

基底クラスはテンプレートで書かれているので、
派生クラスが具体化されるまで fa() が見えない。
よって、次のように書く。

template <typename T>
class A{
  public:
    void fa(){};
};

template <typename T>
class B : public A<T>{
  public:
    void fb(){
      this->fa();
    }
};

もしくは、次のようでも良い。

template <typename T>
class A{
  public:
    void fa(){};
};

template <typename T>
class B : public A<T>{
  public:
    using A<T>::fa;

    void fb(){
      fa();
    }
};

44項、テンプレートでの実行バリナリ重複を最小限にする

テンプレートでは次のような使い方をすることがあります。

A<int ,5> a;  // int 型の要素を5つ持つクラス

このように要素数を持つテンプレートクラスは次のように書けます。
このクラスは各要素を 0〜要素ー1 で初期化し、すべての和を返す f() を持ちます。

template<typename T, int n>
class A{
  T d[n];
  public:
    A(){
      for(int i = 0; i < n; i++){d[i] = i;}
    }
    int f(){
      int total = 0;
      for(int i = 0; i < n; i++){total += d[i];}
      return total;
    }
};

このテンプレートクラスはどのような要素数においても的確に動作します。
ただし、コード中で、

A<int ,1> a1;
A<int ,2> a2;
A<int ,3> a3;

と要素数が異なるオブジェクトを複数作ったとします。
このときにコンパイラはなんと各要素数に応じた実行バイナリを作成するのです。
すなわち、a1 の f()、 a2 の f()、 a3 の f() を作成します。
a1 、a2、 a3 が f() で同じ動作をするのであれば f() の実行バイナリはひとつにまとめたいと思います。
この場合、次のように対応します。

template<typename T>
class A{
  int n;
  T *d;
  protected:
    A(int n, T *d):n(n), d(d){
      for(int i = 0; i < n; i++){d[i] = i;}
    };
    int f(){
      int total = 0;
      for(int i = 0; i < n; i++){total += d[i];}
      return total;
    }
};

template<typename T, int n>
class B : private A<T>{
  T d[n];
  public:
    B():A<T>(n, d){};
    int f(){return A<T>::f();}
};

class B の n は巨大になるかもしれません。
その場合はメモリを動的に割り振るのが良いでしょう。

45項、テンプレートクラスの型変換

つぎのようなクラスA、クラスBがある。

class A{
  int n;
  public:
    A(int n):n(n){};
    void f(){std::cout << "a" << n << std::endl;}
};

class B : public A{
  public:
    B(int n):A(n){};
};

このとき次のようにすればクラスBのコンストラクタでクラスAのオブジェクトを作成できる。

A a1 = B(5);

テンプレートではどうだろうか?
次のようなことはできるだろうか?

C<A> ca = C<B>(new B);

これはできない。
C<A> と C<B> は継承云々ではなく別物として扱われる。

これを実現するにはテンプレートクラスを次のように書く。

template<typename T>
class C{
  T *p;
  public:
    C(T *p):p(p){};
    template<typename U>
    C(const C<U> &c):p(c.get()){};
    T *get() const {return p;}
};

たとえば、次のような処理が書ける。

B *b = new B(3);

C<A> ca = C<B>(b);

A *a = ca.get();

a->f();

delete b;

このような処理はあまり無いように思うかもしれない。
しかし、実際スマートポインタは内部でこの仕組みを持っている。

この仕組みにコピーコンストラクタ、コピー代入演算子を導入する場合には注意がいる。
この仕組みに加えて、通常のコピーコンストラクタ、コピー代入演算子も書く必要がある。

46項、テンプレートを引数にするテンプレート関数を書くには

テンプレートのオブジェクトを引数にするテンプレート関数を書いてみます。
次のようにすればできそうです。

template<typename T>
class A{
  T n;
  public:
    A(const T &n):n(n){};
    const T get() const {return n;}
};

template<typename T>
const A<T> operator*(const A<T> &a1, const A<T> &a2){
  return A<T>(a1.get() * a2.get());
}

しかし、次の処理はコンパイルエラーです。

A<int> a1(3);

A<int> a2 = a1 * 4;

std::cout << a2.get() << std::endl;

これは a1 * 4 を評価するときに a1 の T の情報を得ることができないからです。

この問題は次のように対応します。
次のように friend を使えば期待した結果が得られます。

template<typename T>class A;

template<typename T>
const A<T> mult(const A<T> &a1, const A<T> &a2);

template<typename T>
class A{
  T n;
  public:
    A(const T &n = 0):n(n){};
    const T get() const {return n;}

    friend const A<T> operator*(const A<T> &a1, const A<T> &a2){
      return mult(a1, a2);
    }
};

template<typename T>
const A<T> mult(const A<T> &a1, const A<T> &a2){
  return A<T>(a1.get() * a2.get());
}

これはクラス内の friend 関数で T を確認してからテンプレート関数を呼んでいます。
クラス内に実装を書いてしまえば良い気がしますが、inline になるので長いコードは書けません。
クラスの宣言をヘッダに書いて、テンプレート関数をソースに書くことで宣言と実装をわけることができます。

47項、テンプレートで型情報を得るには

テンプレートを書いていると以下のように書きたいかもしれません。
しかし、このような書き方はできません。

template<typename T>{
void f(T &w){
  if(T の型を判断){

  }
  else{

  }
}

次のように書けば上と同じの動作を書くことができます。

template<typename T>
struct traits{
  typedef typename T::category category;
};

struct type_a{};
struct type_b: public type_a{};

class A{
  public:
    typedef type_a category;
};

class B{
  public:
    typedef type_b category;
};

template<typename T>
void f(T &w, type_a){
  std::cout << "A" << std::endl;
}

template<typename T>
void f(T &w, type_b){
  std::cout << "B" << std::endl;
}

template<typename T>
void f(T &w){
  f(w, typename traits<T>::category());
}

テンプレート関数 f の引数がAかBかで違う動作をします。
この仕組みはイテレータの advance で実際に使われています。
また、次の項で述べるテンプレートメタプログラミングでもあります。

48項、テンプレートメタプログラミング

テンプレートメタプログラミングとは、
実行時に行う計算をコンパイル時に行ってしまおうという方法のこと。

たとえば、次のコードは階乗を計算します。

template<unsigned n>
struct Factorial{
  enum{value = n * Factorial<n - 1>::value};
};

template<>
struct Factorial<0>{
  enum{value = 1};
};

次のように書くと5の階乗を表示します。

std::cout << Factorial<5>::value << std::endl;

この計算はコンパイル時に行われるので、実行バイナリも短くなり、実行速度も上がります。
また、コンパイル時にエラーがでるようにすれば、実行時にエラーがでることはありません。

欠点はコンパイルに時間がかかることです。

49項、メモリの確保はどうやるか?

まずメモリの確保が失敗した場合に呼ぶ関数を用意します。

void OutOfMem(){}

次にその関数のポインタをセットします。

std::set_new_handler(OutOfMem);

メモリが確保できなかったときにどのような動作をするでしょう?
答えはメモリの確保を試み続けます。
もちろん失敗するたびに失敗の関数を呼び続けます。

この仕組みは失敗をしたのであれば次の処理に移ることができることを意味します。

たとえば次のような対処法があります。

1、最初に大きなメモリを用意しておきます。メモリ確保に失敗したのであれば最初に確保した大きなメモリを解放します。再度メモリの確保を試みます。このときにメモリの量が残り少ないことを警告することもできます。

2、メモリの確保の方法を変える。使用メモリの量が少ないアルゴリズムに切り替えるなどすれば可能です。

3、例外処理に切り替える。set_new_handler に 0 をセットすれば次の失敗では例外処理に切り替えられます。

4、例外を投げる。その場から例外を投げます。

5、abort や exit で閉じる。

このようにバリエーションに富んだ対応が可能です。

set_new_handler を指定しなかった場合は例外が呼ばれます。
例外が呼ばれればメモリの確保は失敗するので、メモリが無いものとして以降の処理を書く必要があります。

次のようにすれば例外は呼ばれません。

A *a = new (std::nothrow)A();

例外を呼ぶ代わりに NULL を返します。
ただし、これは A のオブジェクトのメモリが確保できない場合です。
コンストラクタ内の new やその他のエラーではやはり例外が投げられます。

以下のように書くとクラスAのメモリが確保できなかった場合に独自の関数を呼びます。

class NewHandlerHolder{
  public:
    explicit NewHandlerHolder(std::new_handler nh):handler(nh){}
    ~NewHandlerHolder(){
      std::set_new_handler(handler);
    }
  private:
    std::new_handler handler;
    NewHandlerHolder(const NewHandlerHolder&);
    NewHandlerHolder& operator=(const NewHandlerHolder&);
};

class A{
  public:
    static std::new_handler set_new_handler(std::new_handler p)throw();
    static void* operator new(std::size_t size)throw(std::bad_alloc);
  private:
    static std::new_handler currentHandler;
};

std::new_handler A::currentHandler = 0;

std::new_handler A::set_new_handler(std::new_handler p)throw(){
  std::new_handler oldHandler = currentHandler;
  currentHandler = p;
  return oldHandler;
}

void* A::operator new(std::size_t size)throw(std::bad_alloc){
  NewHandlerHolder h(std::set_new_handler(currentHandler));
  return ::operator new(size);
}

void outOfMem(){
  std::cout << "Out of Memory error" << std::endl;
}

例えば次のように書くとメモリを確保できなかった場合に outOfMem を呼びます。

A::set_new_handler(outOfMem);

A *p = new A;

50項、new と delete を自分で定義する時とは

大きく分けて「ログをとる」ことと「カスタマイズする」ことになります。
具体的には以下のようなことがあります。

1、new に対して正確に delete が行われたか調べるため
2、システムにあわせてカスタマイズして効率を上げるため
3、new と delete の使用の統計を取るため
4、ディフォルトが作成する無駄な確保を取り除くため
5、アラインメントに対応するため
6、特別な処理を追加するため

51項、new と delete を自作する

50項で new と delete を自分で定義することを書いたが、実際にどのように書くのか?
以下のように書ける。

class A{
  public:
    static void *operator new(std::size_t size)throw(std::bad_alloc);
    static void operator delete(void *rawMemory, std::size_t size)throw();
};

void *A::operator new(std::size_t size)throw(std::bad_alloc){
  if(size != sizeof(A)){
    return ::operator new(size);
  }

  while(true){
    char *p = new (std::nothrow)char[size];

    if(p != NULL){
      return p;
    }

    std::new_handler globalHandler = std::set_new_handler(0);
    std::set_new_handler(globalHandler);
    if(globalHandler)(*globalHandler)();
    else throw std::bad_alloc();
  }
}

void A::operator delete(void *rawMemory, std::size_t size)throw(){
  if(rawMemory == 0)return;
  if(size != sizeof(A)){
    ::operator delete(rawMemory);
    return;
  }
  delete [] ((char *)rawMemory);
  return;
}

52項、プレースメントの new と delete 対策

次のようなものをプレースメントの new といいます

operator new(std::size_t size, void *p)

次のようにして使えます。

int b = 0;

A *a = new (&b)A();

このような new を作成した場合にいくつかの問題が起きます。

たとえば、

A *a = new A();

と書いたときに通常の new が隠蔽されて見つからなくなってしまいます。

delete のときにも問題があります。

A *a = new (&b)A();

の new は成功したが A() が失敗したとします。
このときC++のランタイムは自動で delete を呼びますが、このときにプレースメントの delete を必要とします。

また、new も A() も成功した場合に delete を呼ぶ場合は通常の delete が必要になります。

すなわち、プレースメントの new を使いたいときは通常の new とプレースメントの delete と通常の delete が必要になるということです。

次のように書いて対応します。

class Ab{
  public:
    static void *operator new(std::size_t size)throw(std::bad_alloc){
      return ::operator new(size);
    }
    static void operator delete(void *pMemory)throw(){
      ::operator delete(pMemory);
    }
    static void *operator new(std::size_t size,void *p)throw(){
      return ::operator new(size, p);
    }
    static void operator delete(void *pMemory, void *p)throw(){
      ::operator delete(pMemory, p);
    }
    static void *operator new(std::size_t size, const std::nothrow_t &nt)throw(){
      return ::operator new(size, nt);
    }
    static void operator delete(void *pMemory,const std::nothrow_t &)throw(){
      ::operator delete(pMemory);
    }
};

class A : public Ab{
  public:
    using Ab::operator new;
    using Ab::operator delete;

    static void *operator new(std::size_t size,void *p)throw(std::bad_alloc);
    static void operator delete(void *pMemory, void *p)throw();
};

53項、コンパイラの警告に注意を払おう

・コンパイラの警告をまじめに受け取ろう
・警告レベルが最高でも警告がでないようにしよう
・警告はコンパイラによって異なる

54項、TR1を含む標準ライブラリになじもう
55項、Boostに親しもう

inserted by FC2 system