読者です 読者をやめる 読者になる 読者になる

継承され得るクラスの作法の話など。

C++

twitter にて @fadis_ センセのこの呟き。

それに対する @cpp_akira センセとのリプライがこちら。

 興味深いですねー、はい。

 クラスを継承するってことは、そのクラスの特徴を引き継いで新しいクラスを定義するってことですが、説明しにくいのでコードを書きます。

class Base
{
public:
    Base();
    virtual void ~Base();

    void foo( int i );
};

class Derived : public Base
{
public:
    ~Derived();
};

Base を定義するにあたって基本的な考え方は、
 「今後 Base をどのように利用されようと大丈夫なように作っておこう」
ということですね。で、それに則ると
 「今後 Base を継承して任意のクラスを定義したりも可能であるべき」
となります。従って
 「Derived::~Derived がちゃんと呼ばれるように Base::~Base は virtual にしておくべき」
という指針が導かれる、と。ここまで考えれば至極まっとうなのですよ。


さて、例示したコードの場合ですが、Derived には以下の特徴が引き継がれます。


・基底クラスの名前は Base である。
・Base は他のいかなるクラスも継承していない。
・Base のメンバは全て public である。
・Base はデフォルトコンストラクト、コピーコンストラクト、代入演算、デストラクトが可能である。
・関数 foo がある。戻り値は void で引数は int がひとつで、非 const である。
 例えば後から Base::Base や Base::~Base のコードに変更が発生したって Derived 側を修正する必要はありません。Base の責務の範囲で Base の諸事情をおさめるという指針ですね。(この言い回しに突っ込みたい人が多数居ると思われますが、まぁ、うん。はい。)

 しかしながら、C++ は速度やメモリをカリカリする言語です。実は Base を継承したときに
・Base のデストラクタは仮想関数である。
という特徴も引き継がれるのです。これはさっきの指針を覆します。@cpp_akira センセがリプライで説明しているのはまさにこの重要性です。virtual を付けるということは「Base を利用するなら仮想関数のコスト払えよ」と明言することになります。そういうコストが払えないシーンで利用するクラスの定義においては「virtual を付けることが悪になる」ので、冒頭の指針が足かせになっちゃうのです。

 つまり、仮想関数の有無はインターフェイスの一部なんですね。メンバ関数 foo の戻り値が double ではなく void であるように、引数が string でなく int であるように、デストラクタが仮想です。

まとめると

です。

 他にも、他の言語だけを触った人からは理解されにくい C++ 独自の仕様があります。
 例えば C++ ではジェネリックなコードは全部ヘッダに書かないといけないです。総称型がどう使われているのかが、完全に見えていないとユーザーコードをコンパイルしようがないからです。template は実装まで含めてようやくひとつのインターフェイスです。多次元配列なんかも子階層の要素の数まで含めてようやくインターフェイスです。

 いやぁ C++ ステキですね。