補足:オブジェクトについて


プログラムの作成に慣れ、 だんだん大きなプログラムを作れるようになってくると、 それはそれでまた新たなハードルが見えてきます。 機能を追加しすぎて、 どこにどういうプログラムの部品(関数や変数)を作ったか分からなくなったり、 作ったプログラムを改良しようとして一部を変更したら正常に動かなくなったり、 正常に動いているように見えても何か不審な動作をすることがあったりと、 このハードルはなかなか手ごわい相手になってきます。

人間は忘れる動物ですから、 ちょっと前に作ったプログラムでも、 自分がそのとき何を考えてそういうプログラムを作ったのか思い出せない、 なんてことは当たり前のように起こります。 プログラムを修正したとき、 変更した部分だけを見るとおかしくないのに正常に動かなくなったような場合は、 その変更によってもとのプログラムを作成したときに考えていたロジック (論理、『こういう理由でこういうプログラムを作った』という思考の展開) を壊してしまったことが原因になっていることがよくあります。

自分一人ですらこうなんですから、 これが他人と共同で大規模なプログラムを開発する場合なんかだと、 どういう修羅場になるか想像に難くないでしょう?

コメントや字下げは、 こういう「泥沼」にはまらないようにするための、 もっとも基本的な「予防薬」です。 しかし、プログラムが大規模になってくると、 この薬の効き目もにぶくなってきます。 そういうときは先人の知恵「分割して統治せよ」に学んでみましょう。

プログラムを機能単位に別けて作成し、 変数や関数はその中で局所的に取り扱うようにします。 こうするとプログラムのある機能単位に対する変更が、 他の機能単位に対して影響を与えないようにできます。 逆に、相互に似通った機能単位が2つ以上あるとき、 そのうちの一つを元にして他のものを生成することができれば、 元の機能単位に対する変更を矛盾なく生成した機能単位に反映することができます。 こういう「プログラムの書き方」に対する、 一つのアイデアと言うかポリシーが「オブジェクト指向」です。

ところで、この講義の第1回で、cout のことを「出力ストリームを示すオブジェクト」だと説明しました。 では、このオブジェクトっていったいなんでしょう?

例えば「合計を求めるために変数 sum を用意して 0 で初期化する」という 「仕様」(こんな記述を普通は仕様とは言わないけど)があり、 この実装として double sum = 0.0; という宣言を行ったとします。 変数宣言はポインタのところで述べたように、 コンピュータの内部のメモリの一部を確保して、 そこに「ここは変数 sum が使うよ」というシールを貼るということに過ぎません。 しかしプログラムを作る人は、これに 「データを積算して合計を求めるために使う」という役割を与えます。

プログラムを作成するためには、 そのプログラムによって処理すべき「問題」を、 ソースプログラムという形に書き換えなければなりません。 このため、変数や関数といったプログラムの「構成要素」は、 たとえば変数 sum に与えられた役割のように、 どれもその問題を構成するどこかの部分に対応づけられます。 つまり、これらのプログラムの構成要素は、 問題の一部分を「抽象化」しているのです。

オブジェクトとは、 こういう「何か」を抽象化した、プログラムの構成要素のことを言います。 コンピュータのプログラムやデータ、ハードウェア、周辺機器、 そのほかコンピュータの処理対象となり得るありとあらゆる「もの」を、 オブジェクトとして抽象化することができます。

例えば cout が示している出力ストリームというオブジェクトは、 プログラムが画面表示やデータの出力を行うための対象や機能を抽象化したものです。 プログラムで画面表示を行うためには、 実際にはいろいろ複雑な処理を経る必要があるのですが、 そのような知識をまとめて、 画面表示という 概念抽象化したオブジェクトを作ることで、 みなさんはこれに挿入演算子 << を使ってメッセージを送るだけで画面表示を行うことができるのです。

なじみの喫茶店で、 客が「マスター、コーヒー一杯」と言ったとします。 すると、店のマスターがコーヒーをいれてくれたとします。 これは、客がマスターというオブジェクトに対して、 「コーヒー一杯」というメッセージを送ったのだと見ることができます。 するとマスターは、客がコーヒーの作り方をマスターに逐一指示しなくても、 マスターは自分の知識を使って客に美味しいコーヒーをいれてくれるのです。

1 や 2 のような数値も一つのオブジェクトです。 これらは整数の定数オブジェクトですが、 1.0 や 2.0 のように小数点を持つものは、 これらとは別の実数の定数オブジェクトです。 1.0 + 2.0 という式の見掛けは 1 + 2 に似ていますが、 整数の計算と違って実数の計算では小数点以下も計算しなければなりません。 したがって、これらは同じ "+" という演算子を使っていても、 実際は異なる処理が行われます。 すなわち、オブジェクトがメッセージを受け取ったとき、 どういう処理を実行すべきかはオブジェクト自身が知っているのです。 このように、オブジェクト自身が知っている処理方法のことを、 メソッドと言います。

このようにメソッドがオブジェクトに結び付けられていれば、 異なるオブジェクトに対して同じメッセージを送ったときに、 それぞれ異なる処理をさせることができます。 これはメソッド(処理方法)が実際にはオブジェクトごとに異なっていても、 それらを単一のプログラムの構成要素のように取り扱えることを意味します。 こうするとプログラムを書くときに対象 (オブジェクト)が何かによって処理を変えるような配慮が不要になり、 プログラマはプログラム本来の「意味」を考えることに専念できるようになります。 このように異なるメソッドが一つの概念で総称して表されていることを、 ポリモーフィズム(多態性)と呼びます。

ある喫茶店のマスターは、 コーヒーを注文するとサイフォンでいれてくれますが、 別の喫茶店のマスターはネルでドリップしてくれます。 2つの店のコーヒーの作り方は全然違いますが、 「コーヒーを作る」という概念は共有されているので、 客はどちらの店でも「コーヒー一杯」と注文するだけで 美味しいコーヒーを飲むことができます。

ところで、入出力や数値以外にはどういったオブジェクトがあるのでしょうか。 大半のオブジェクトはプログラムを作る人が自分で定義する必要があります。 プログラムを作る人は、そのプログラムで処理すべき問題を分析し、 その各部をオブジェクトという形で抽象化していくことによって、 目的のプログラムを完成させます。

ただ、このような作業は非常に手間がかかります。 そこで、例えば多くの人が関わると思われる特定の対象を、 あらかじめオブジェクトとして抽象化してまとめたものが用意されています。 このようなものを「クラスライブラリ」といい、 グラフィックスとか、行列計算とか、様々な用途のものが作られています。 データの入出力に関わるものも一つのクラスライブラリとしてまとめられており、 そのうち標準出力に関するものが cout という変数を通じて利用できるようになっているのです。 #include <iostream.h> というおまじないは、 実は「入出力ストリームクラスライブラリ」の定義を読み込んでいるのです。