忘れやすいC++の仕様(3/3)

1つ前のエントリーの続きである。

(5) 純粋仮想関数の定義方法
クラス定義内で、"virtual void foo() = 0;"のように、メソッドのプロトタイプにて、関数=0;とする。
関数の宣言と同時に、関数のポインタの値を0に決めてしまうという感じだろうか。独特の風味だ。

この"=0"を外すと、派生クラスに関数の実体があっても、基底クラスの仮想関数の実体が無いということでコンパイルエラーになるし、"=0"じゃなく"{}"とすると、その基底クラス(仮想クラス)のインスタンスを作ることが可能になってしまう。

(6) デストラクタはvirtualにすると派生クラスのデストラクタも呼ばれる

class CLS{
    …
public:
    virtual ~CLS() {…}  /* (g) */
};

class DRV : public CLS{
    …
public:
    ~DRV() {…}
};

int main()
{
    CLS* p = new DRV;
    delete p;  /* (h) */
}
このコードにおいて、(h)の基底クラスのポインタを使ったdeleteを実行すると、派生クラスのデストラクタDRV::~DRV()と基底クラスのデストラクタCLS::=~CLS()が順に呼び出される。もし(g)のvirtualが無いと、CLS::=~CLS()しか呼び出されない。
従って、基底クラスのデストラクタはvirtualにすべしと言えるだろう。

蛇足だが、派生クラスのポインタを使ってdeleteすると、基底クラスのデストラクタをvirtualにしていなくても、派生クラスのデストラクタの後に基底クラスのデストラクタが呼び出される。

(7) フレンド関数の1つ目の引数は型変換される
フレンド関数の1つ目の引数については、フレンド関数の他に適用できるクラスメソッドや関数やが無く、型変換により適用可能であれば、自動的に型変換されて適用される。
クラス外で定義されるクラスメソッドは、1つ目の引数が型変換されて適用されることは無い。従って、フレンド関数はクラスメソッドよりも適用範囲が広いと言える。

次のコードは、1つの演算に適用できるクラスメソッドとフレンド関数との両方がある例である。

class CLS1{
    friend class CLS2;
    int i;
public:
    CLS1(int i=0){this->i = i;}
    CLS1& operator+(double d){i += (int)d; return *this;}  /* (k) */
};

class CLS2{
    double d;
public:
    CLS2(){d = 0.0;}
    CLS2(CLS1 &i){d = (double)i.i;}  /* (l) */
    friend CLS2& operator+(CLS2, double);  /* (m) */
    double get() const {return d;}
};

CLS2& operator+(CLS2 c2, double e)  /* (m) */
{
    c2.d += e;
    return c2;
}

int main()
{
    CLS1 c1(3);
    CLS2 c2 = c1 + 2.5;  /* (n) */
}
このコードにおいては、(n)の演算において(k)のクラスメソッドがc1.operator+(2.5)という形で呼び出され、演算結果は5.0になるが、もし(k)の行が存在しないと、(n)の演算において、(l)による型変換が自動的に適用されて、(m)のフレンド関数がoperator+(CLS2(c1), 2.5)という形で呼ばれ、演算結果は5.5になる。