ショートカット
ファシリテーター × あり方
コーディングの向こう側
Hello, ANOTHER world!
オブジェクト指向のはなし
プログラミングのはなし
C言語実力診断クイズ
eSkillBooks
オブジェクト指向のはなし

クラスとインスタンスの記述

導入

C++ のクラスは構造体の拡張版です。型を宣言し、メソッドを定義することによって利用可能になります。クラスはオブジェクトの雛形なので、一度定義すればいくつでもインスタンスを生成できます。

用語
このトピックでは次の用語を覚えます:
  • class キーワード
  • クラススコープ
  • メンバ変数
  • メンバ関数(=メソッド)
  • コンストラクタ・デストラクタ
解説

まず、ヘッダファイル中にクラス型を宣言します。例として、名前と年齢を属性に持つ Human というクラスを宣言する場合を考えてみましょう。次のように class キーワードを使います。

SAMPLE CODE
クラス宣言ファイル(ファイル名:"test.h")

#ifndef INCLUDED_test
#define INCLUDED_test


// class 宣言
class Human {
public:
    // コンストラクタ
    Human( const char *pcName, int nAge=0 );
    
    // デストラクタ
    virtual ~Human();
    
    // 属性の取得:「名前」
    const char *Name() { return m_pcName; }
    
    // 属性の取得:「年齢」
    int Age() { return m_nAge; }
    
    // メソッド:「年をとる」
    void IncreaseAge();

private:
    // メンバ変数:「名前」
    const char *m_pcName;
    
    // メンバ変数:「年齢」
    int m_nAge;
};

#endif // #ifndef INCLUDED_test

このクラスには、インターフェイスとして次のメソッドが宣言されています。メソッドはオブジェクトに対して何らかの動作を行わせ、その結果オブジェクトの属性が変化します。メソッドは、メンバ関数で表現されます。「メンバ」とは、そのクラスに属するという意味で使われる言葉です。

  • IncreaseAge

次の2つもメソッドには違いありませんが、ただ単に属性を取得するためのものなので少し意味合いが違います(このようなメソッドの存在意義についてはあとで説明します)。

  • Name
  • Age

次の2つはメンバ変数と呼ばれるもので、属性の実体(実装)です。インターフェイスではありません。これらはインスタンスが生成されるたびに複製され、オブジェクト固有の値を保持できるようになります。

  • m_pcName
  • m_nAge

クラスにはコンストラクタおよびデストラクタと呼ばれる2つの特別なメンバ関数があります。これらはインスタンスの生成直後および破棄直前に呼び出されるもので、メンバ変数の初期化と後かたづけを行う場所です。

  • Human
  • ~Human

宣言と定義が必要なのは C言語と同じです。クラスの宣言を行ったら、今度はその定義を行います。実際にはクラス中のメソッドをメンバ関数として定義します。

メンバ関数の定義には、その関数がどのクラスに属するのかを示すために :: という記号を使います。Human クラスの関数なら

Human::関数名()

となります。実は一般に、クラスに属する識別子をクラス宣言の外側で示すためにこの記法を使います。

ではメンバ関数の定義例を見てみましょう。

SAMPLE CODE
クラス定義ファイル(ファイル名:"test.cpp")

#include <assert.h>
#define ASSERT(C) assert(C)

#include "test.h"

// コンストラクタ定義
Human::Human( const char *pcName, int nAge )
{
    ASSERT( pcName );
    
    m_pcName = pcName;
    m_nAge   = nAge;
}

// デストラクタ定義
Human::~Human()
{
}

// メソッド:「年をとる」
void Human::IncreaseAge()
{
    m_nAge++;
}

まずクラス宣言をインクルードしています。これは C言語でプロトタイプをインクルードするのと同じような理由です。

コンストラクタについては以前に簡単な説明をしましたが、ここではメンバ変数の初期化を行っています。これを行わないと、メンバ変数の初期値は不定(いわゆるゴミ)になります。これは C++ コンパイラがさぼっている訳ではなくて、勝手なことをして余計な実行時間をかけないように配慮されているのです。初期化をするもしないもプログラマの責任で、ということです。ちなみにこの例ではデフォルトパラメータが利用されています。

デストラクタはメンバ変数の後かたづけを行うためのものですが、この例ではとくに必要ないので空っぽの定義になっています。デストラクタが活躍するのはおもにコンポジションを行うときですが、これは別の機会にお話しします。また、クラス宣言ではデストラクタに virtual というキーワードがついていますが、これについてはポリモーフィズムの記述方法の説明を行うときに詳しくお話しします

メソッド IncreaseAge は、年齢を1つ増やすためのものです。実際にはメンバ変数 m_nAge を1つ増やしています。メンバ関数の内部では、スコープがそのクラスになります。つまり、とくに :: などの記号を用いなくてもただメンバ名を書くだけでクラス宣言内にある識別子にアクセスできます。このスコープをクラススコープと呼びます。よく見ると、コンストラクタでもこの性質が使われていますね。

少し詳しい話をすると、メンバ変数へのアクセスは、インスタンスを指すポインタを通して暗黙的に行われています(this という名前のポインタです)。このポインタを明示的に書くこともできますが、単純化のため省略できる仕組みになっているのです。この仕組みを理解していれば、C言語でもクラスとインスタンスの構造を実現できます(やり方は考えてみて!)。

これでクラスが定義されたので、今度はその使用方法を説明します。一度クラスができてしまえばいくつでもインスタンスを生成することができ、それぞれのインスタンスが独自の属性を保持できます。次のコードを見てください。

SAMPLE CODE
クラスの使用例(ファイル名:"main.h")

#include <iostream>
using namespace std;

#include "test.h"


// main 関数
int main()
{
    // 花子さん誕生
    Human Hanako( "Yamada Hanako" ); ... (1)
    
    // 太郎くんはいま26歳
    Human Taro( "Yamada Taro", 26 ); ... (1)
    
    // 2人は1つずつ年をとりました
    Hanako.IncreaseAge(); ... (2)
    Taro.IncreaseAge(); ... (2)
    
    // 今何歳?
    cout << Hanako.Name() << " ... " << Hanako.Age() << endl; ... (2)
    cout << Taro.Name()   << " ... " << Taro.Age()   << endl; ... (2)
    
    return 0;
} ... (3)

インスタンスの生成は

クラス名 インスタンス名(コンストラクタへの引数);

という書式になります。上のコード中の (1) がこれにあたります。このときにコンストラクタが起動され、オブジェクトの属性が初期化されます。

メンバへのアクセスは、構造体のメンバへのアクセスと同じようにします。つまり実体のときは . (ピリオド)を使い、ポインタの時は -> を使います。そこでメソッドは (2) のようにして呼び出すことができます。

(3) のようにインスタンスがスコープアウトする場所でデストラクタが呼び出されます。インスタンスは通常のローカル変数と同様にスタック上に確保されるのです。なお、new/delete 演算子でインスタンスをヒープに確保することもできるのですが、その方法・用途については後で説明します。

さて、あとは public/private と属性の取得を行う関数についての説明が残っていますが、これは次回のトピックで。

もっと!
もっと詳しく知りたい人は次の用語について調べると良いでしょう:
  • デフォルトコンストラクタ
  • コピーコンストラクタ
  • this ポインタ

(「オブジェクト指向のはなし」は1999年2月から2000年4月にかけて作成されたコンテンツです。)