オブジェクトの動的生成とポインタ


このページでは、仮想関数を学ぶ上で必要となる基礎事項をまとめておきます。

オブジェクトのスコープとコンストラクタ・デストラクタ

これまでは、クラスのインスタンスを関数の自動変数として作成して使ってきました。自動変数は、そのスコープに入った時にスタックと呼ばれる場所に自動的に作成され、処理の流れがスコープから抜けると自動的に消滅します。コンストラクタとデストラクタが定義されたクラスでは、それらも自動的に呼び出されました。

C++言語における(C言語でも同様)自動変数のスコープは、ブロックと呼ばれる単位で決定されることに注意しましょう。対応した中カッコで囲まれた範囲 {...} がブロックです。関数も一つのブロックで構成され、その内部の for ループや whileループ、if 文を実行する範囲もひとつのブロックとなります。ブロックは入れ子(ネスト)構造にすることができます。

右の図は、関数 foo 内でインスタンス化された Object クラスのオブジェクト obj1、obj2、obj3に対するコンストラクタ・デストラクタがいつ呼び出されるかを示しています。

コンストラクタは、それぞれのオブジェクトが定義された時点で呼び出されます。obj1とobj3のデストラクタは、関数 foo から抜ける時点で呼び出されます。obj2 は forループのブロック内で定義されていますので、forループから抜ける時にデストラクタが呼ばれます。


多重継承におけるコンストラクタとデストラクタの呼び出し順序

継承をつかって定義したクラスのコンストラクタは、その継承関係の基本クラスから順番に呼び出されます。この際に、基本クラスの引数を持ったコンストラクタを呼び出すには、派生クラスのコンストラクタのイニシャライザで初期化してあげる必要がありました。

デストラクタの呼び出し順序はこれとは逆に、派生クラスのほうから順に呼び出されます。デストラクタに引数を持たせることはできません。

オブジェクトの動的生成

C言語では、malloc関数を使って動的に(実行時に)オブジェクトにメモリを割り当てることができました。C++言語では、これを new という構文を使って行います。

オブジェクトを new すると、システムはヒープと呼ばれるメモリ範囲にオブジェクトを割り当て、コンストラクタを自動的に呼び出した後にオブジェクトのアドレス(ポインタ)を返します。

Object クラスのインスタンスを動的に作成するには、以下のように記述します。

配列としてオブジェクトを new する場合には、コンストラクタに引数を与えることはできず、デフォルトコンストラクタが呼び出されます。

オブジェクトの破棄

動的に作成したオブジェクトの破棄には、delete を使用します。オブジェクトのデストラクタが呼び出された後に、割り当てられたメモリが解放されます。

    delete p1;

    delete[] p4;   // 配列として割り当てたオブジェクトの破棄     

配列として割り当てたオブジェクトの解放には delete[] を使います。これにより、複数のオブジェクトに対するデストラクタの呼び出しをコンパイラに指示します。

動的に作成したオブジェクトは、それを指しているポインタのスコープから外れても自動的に破棄されることはありません。使用の終わったオブジェクトはプログラマが責任を持って破棄する必要があります。

ポインタを介したオブジェクトへのアクセス

ポインタを介してオブジェクトのメンバにアクセスするにはアロー演算子( -> ) を使用します。

ポインタの復習

ちょっとだけポインタの復習もしておきましょう。C言語でも既に学びましたが、ポインタの実態は特定のオブジェクトのアドレスを格納したものでしたね。ポインタはアドレスを格納しているだけなので、その大きさは(この32bit処理系では)常に4バイトでした。

常にアドレスしか格納していないにもかかわらず、ポインタ変数にもがあります。ポインタ変数における型とは、そのポインタが指している先(ポインタに格納されたアドレスの内容)を、どのように解釈しなければならないかを表しています。

右の図のような値が格納されたメモリ領域があったとします。そして、char、int、double を指すポインタ p_char, p_int, p_double がそれぞれ同じアドレスを指しています。

それぞれのポインタが指しているさきは同一アドレスですが、p_charを通じて間接参照した場合には、ポインタが指している先から1バイトが評価されて 0x61 ('a'のキャラクターコード)となります。

p_int を通じて間接参照した場合には4バイトが評価されて、0x00636261() となり、6513249という整数値となります。

p_doubleを通じて間接参照した場合には8バイトが浮動小数点フォーマットに従って評価されて、100000.・・・・ という実数値となります。

このようにポインタの型は、間接参照した際の値の評価に重要な意味を持っています。

なぜ図のメモリ列がこのような4バイトの値になるかは、エンディアンをキーワードにして調べてみてください。)


次に、クラスのメンバ変数に対してアロー演算子を使って間接参照する際のイメージを右の図に示します。

メモリ上のクラスAのオブジェクトをポインタ p が指しています。この時、p->m3 のようにメンバの参照を行うと、メモリレイアウト上でのメンバm3の位置のずれ(オフセット)がコンパイラによって自動的に加算されてから間接参照が行われます。

 

メンバ関数呼び出しの生成

Aというクラスオブジェクトを指しているポインタ p があった時に、p->function(); のようなポインタを介した(非仮想の)メンバ関数の呼び出しに対して、コンパイラは単純に A::function という名前の関数呼び出しを生成します。

この時にメンバ関数にはオブジェクトを指すポインタ(この場合はp)が隠し引数として渡されます。これがメンバ関数内で this として参照できるポインタ(this ポインタ)です。thisポインタの存在により、メンバ関数内ではどのインスタンスに対する処理かを区別し、適切なインスタンスの操作を行っています。