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

クラスとは何か。

設計

プログラムを書く人にとって、クラスとは何なのでしょうか。
シンプルな例を出して考えると理解が速いですよね。

class A
{
public:
    string name_;
    int    price_;
};

名前と値段のフィールドがありますね。
はい。クラスとはデータ集合の定義のことです。

ですがこれだけでは使いようがありませんね。
では次の例を見てください。

int main()
{
    int i;
    double d;
    A a;
}

int, double, … と同じように A を使っていますね。
クラスとは型なのです。*1


おっと、忘れていました。
実はクラスには関数を定義する機能があるのです。

class A
{
public:
    string name_;
    int    price_;
    string to_string();  // 名前と値段を用いて文字列を生成する。
};

はい。クラスとはデータとその操作をひとまとめにした定義のことです。




ところで、データと関数をひとまとめにすると何が良いんですかね?
この例だと分かりやすいかも。

class A
{
public:
    string name_;
    bool   is_visible();
    bool   is_enable();
};

A のインスタンスは、
 ・名前を持っている
 ・可視であったり無かったりする
 ・有効であったり無効であったりする
…と、関数を使うことでこういう表現が可能になります。
つまり、クラスとは特徴の集合のことなんです。

しかし、特徴の表現をするにしては、コードがやたらと分かりにくいと思いませんか。ついさっき書いた「可視であったり無かったりする」みたいな表現のほうがずっと分かりやすい。実はこの分かりにくさには理由があるのです。この例を見てください。

class A
{
    void set_name( string name );
};
int main()
{
    A a;
    a.set_price( 1000 ); // ←コンパイルエラー
}

コンパイルエラーというやつです。
クラスとは、そもそもプログラマの間違いをコンピュータに検出させるための機構だったんです。

さて、僕は先日こんなクラスを書きました。

class A : noncopyable {};
int main()
{
    A a1, a2;
    a1 = a2; // ←コピーしようとしたらコンパイルエラー
}

またコンパイルエラーです。noncopyable を継承したクラス A は、コピーが不可能になります。コピーが不可能なのにコピーしようとしたからエラーになってるんですが、実はこの noncopyable だってクラスなんです。
つまりクラスとは、他のクラスの性質を明示するためのものでもあるのです。




ところでジェネリックプログラミングが最近注目を浴びているように思います。こんなやつですね。

template < typename T >
class Node
{
   T * prev;
   T * next;
};

これは、任意の型のオブジェクトを prev, next で参照する Node クラスの定義です。
はい。クラスとは構造のパターンを定義するために使うものです。

もう一例、ジェネリックプログラミングの威力を見てみましょう。*2

int main()
{
    typedef fsn = boost::fusion;
    class x {}; class y {}; class z {};
    fsn::map<
        fsn::pair< x, char >,
        fsn::pair< y, int >,
        fsn::pair< z, double >
    > v( 'a', 10, 0.3 );
    
    assert( fsn::at_key< x >( v ) == 'a' );
    assert( fsn::at_key< y >( v ) == 10 );
    assert( fsn::at_key< z >( v ) == 0.3 );
}

これは、キーと値の型が全て異なる map を使ったコードです。boost::fusion を使っています。この例では、x, y, z は互いの識別以上の意味を持ちません。
つまりクラスとはラベルなのです。




ところでプログラムでは「適切な名前をつけよう」とよく言われます。しかし、あんまり考えずに名前をつけていくと、他のクラスと衝突するかもしれません。それは嫌ですよね。

class A { class Type { }; };
class B { class Type { }; };

ここで Type という名前が 2 つありますが、これは衝突とみなされません。
そうです。クラスというのは名前の分割単位なのです。

名前というとこういうコードを連想します。

void foo( A a );
void foo( B b );

これは、「foo という関数は引数が A または B である場合にのみ呼び出せる」という表明にほかなりません。
クラスとはプログラマとコンピュータをつなぐ契約なのです。

クラスは GUI プログラミングにおいても利用されますよね。

class my_button : public button
{
    virtual void on_clicked( position pos );
};

こういうの、よくありますよね。button はライブラリで用意されているクラスです。いわゆる「on_clicked をオーバーライドして、ボタンが押されたときの処理を書いてね」というやつです。my_button の実装をする人は、どうやってクリックを検知し、通知し、処置し、それを完了するのか、知らなくて良いのです。
そう、つまりクラスとはサービスなのです。




さて、一歩、後ろに下がってみましょう。プログラムを作ろうとするとき、よく設計が大事だとか言われますよね。どんなふうにプログラムを組み立てるのかを事前に考えたり、チームで意思疎通をするためにクラス図を使う事が出来ます。

個々のボックスがそれぞれクラスです。こうやって図にすることでプログラム全体を把握しやすくなります。
クラスというのは、プログラムの構成要素である*3という事がよくわかります。



さて、では最後にふりかえってみます。
ここまで紹介してきた「クラスとは○○である」のサンプル群から何がわかるでしょう?実は、結局のところ僕にはクラスが何なのか分からないのです。そもそもプログラムはソースさえあれば設計書が無くても動きます。たとえプログラマに十分な概念の理解が無くても動くのです。そしてクラスの書き方は、文法によってのみ決められています。ただ、それだけなのです。
つまりクラスが何であるかという問いには、文法に則って記述された「何らかの意味」であるということぐらいしか言えないと思うのです。

*1:オブジェクト a は集合 A の要素である、という見方もできますね。

*2:ネタ元:『[http://d.hatena.ne.jp/faith_and_brave/20101115/1289804239:title=boost::fusion::map]』 Faith and Brave - C++で遊ぼう

*3:オブジェクト指向設計で言うとクラスは責務の単位である、ということになるのかな。