null値とis演算子

前の章で、参照型の変数は特定のオブジェクトを参照する必要はないことを学んだ。

MyClass referencesAnObject = new MyClass;

MyClass variable;   // オブジェクトを参照していない
D

参照型である上記のvariableには識別子があるが、まだどのオブジェクトも参照していない。このようなオブジェクトは、次の図のようにメモリ内の場所にあると想像することができる。

      variable
   ───┬──────┬───
      │ null │
   ───┴──────┴───

値を参照しない参照は、nullである。これについては、以下で詳しく説明する。

このような変数はほぼ無用の状態である。参照するMyClassオブジェクトが存在しないため、実際のMyClassオブジェクトが必要な文脈では使用できない:

import std.stdio;

class MyClass {
    int member;
}

void use(MyClass variable) {
    writeln(variable.member);    // ← バグ
}

void main() {
    MyClass variable;
    use(variable);
}

use()が受け取るパラメーターが参照するオブジェクトが存在しないため、存在しないオブジェクトのメンバにアクセスしようとすると、プログラムがクラッシュする:

./deneme
セグメンテーションフォールト
Bash

"セグメンテーションフォールト"は、不正なメモリアドレスにアクセスしようとしたために、オペレーティングシステムによってプログラムが終了したことを示している。

nullの値

特別な値nullは、他の値と同じように表示できる。

writeln(null);
D

出力:

null

null変数は、次の2つの文脈でのみ使用できる:

  1. オブジェクトを代入する
    variable = new MyClass;  // 現在はオブジェクトを参照している
    D

    上記の代入により、variableは新しく構築されたオブジェクトへのアクセスを提供する。この時点から、variableは、MyClass型の有効な操作に使用できる。

  2. nullであるかどうかを判断する。

    ただし、==演算子は比較する実際のオブジェクトを必要とするため、以下の式はコンパイルできない。

    if (variable == null)     // ← コンパイルエラー
    D

    そのため、変数がnullであるかどうかは、is演算子によって決定する必要がある。

is演算子

この演算子は、"null値を持つか"という質問に答える。

if (variable is null) {
    // どのオブジェクトも参照していない
}
D

isは、他の型の変数にも使用できる。以下の使用例では、2つの整数のvaluesを比較している。

if (speed is newSpeed) {
    // 値は等しい

} else {
    // 値は異なる
}
D

スライスと組み合わせて使用すると、2つのスライスが同じ要素のセットを参照しているかどうかを判断する:

if (slice is slice2) {
    // 同じ要素へのアクセスを提供する
}
D
!is演算子

!isは、isの逆である。値が異なる場合、trueを返す。

if (speed !is newSpeed) {
    // それらの値は異なる
}
D
nullの値の代入

nullの値を参照型変数に代入すると、その変数は現在のオブジェクトへの参照を停止する。

その代入が実際のオブジェクトへの最後の参照を終了させる場合、実際のオブジェクトはガベージコレクタによる最終化の対象となる。結局、どの変数からも参照されていないオブジェクトは、プログラム内で使用されていないことになる。

前の章で、2つの変数が同じオブジェクトを参照していた例を見てみよう。

auto variable = new MyClass;
auto variable2 = variable;
D

以下の図は、そのコードを実行した後のメモリの状態を表している:

  (anonymous MyClass object)    variable    variable2
 ───┬───────────────────┬───  ───┬───┬───  ───┬───┬───
    │        ...        │        │ o │        │ o │
 ───┴───────────────────┴───  ───┴─│─┴───  ───┴─│─┴───
              ▲                    │            │
              │                    │            │
              └────────────────────┴────────────┘

nullの値をこれらの変数の1つに代入すると、その変数とオブジェクトとの関係が切断される。

variable = null;
D

この時点では、MyClassオブジェクトを参照しているのはvariable2だけになる:

  (anonymous MyClass object)    variable    variable2
 ───┬───────────────────┬───  ───┬────┬───  ───┬───┬───
    │        ...        │        │null│        │ o │
 ───┴───────────────────┴───  ───┴────┴───  ───┴─│─┴───
              ▲                                  │
              │                                  │
              └──────────────────────────────────┘

最後の参照にnullを代入すると、MyClassオブジェクトが到達不能になる:

variable2 = null;
D

このような到達不能なオブジェクトは、将来のどこかの時点でガベージコレクタによって破棄される。プログラムの観点からは、オブジェクトは存在しないことになる:

                                variable      variable2
 ───┬───────────────────┬───  ───┬────┬───  ───┬────┬───
    │                   │        │null│        │null│
 ───┴───────────────────┴───  ───┴────┴───  ───┴────┴──

関連配列の章の演習セクションで、関連配列を空にする方法について議論した。現在、4つ目の方法を知っている:nullを関連配列変数に代入すると、その変数と要素の関係が切断される:

string[int] names;
// ...
names = null;     // どの要素にもアクセスできない
D

MyClassの例と同様に、namesが連想配列の要素への最後の参照であった場合、それらの要素はガベージコレクタによって最終化される。

スライスにもnullを代入できる:

int[] slice = otherSlice[ 10 .. 20 ];
// ...
slice = null;     // どの要素にもアクセスできない
D
要約