SE(たぶん)の雑感記

一応SEやっている筆者の思ったことを書き連ねます。会計学もやってたので、両方を生かした記事を書きたいと考えています。 でもテーマが定まってない感がすごい。

オブジェクトとインスタンス、そしてガベージコレクションについて

オブジェクト指向の言語を使ってプログラミングしていると、ふと思うのが、このインスタンスはいつ消えるの…?という疑問です。*1

まあ、C#等のガベージコレクション(Garbage Collection)を持っている言語を使う場合、ほとんど気にする必要はないのですが、知っておいたほうが良い内容ではあります。

誠に勝手な言い分ですが、プログラミング未経験、オブジェクト指向未経験な人にとっては、理解しがたい内容だと思います。すみません。

オブジェクトとインスタンスについて

これは、いろんな説明がありますので、持論ということでお願いしたいです。

私は、オブジェクトは利用対象であり、インスタンス実体と考えています。
といっても、分かりづらいと思いますが、要するに「見る人によって、それがオブジェクトかインスタンスか変わる」ということです。

図にしてみると、

種類 説明 視点
オブジェクト どう使うか、だけ考えればよい 利用者
インスタンス 消えるまで面倒見ないといけない 作成者

です。

以下のようなインターフェイスと、それを実装したクラス、さらに利用者と呼び出し元がいるとします。

///「吠える」ことができる
public interface IBarkable {
    //吠える
    string Bark();
}
  • 実装クラス
///犬
public class Dog : IBarkable {

    public string Bark() {
        return "わんわんお!";
    }
}

///狼
public class Wolf: IBarkable {

    public string Bark(){
        return "わおーん!";
    }
}
///見回りさん
public class SecurityGuard {
    //吠えるやつ連れてパトロール
    public void Patrol(IBarkable barker) {
        //見回りしていたら、なんか見つけたので、吠えさせた
        if (なんか見つけた) {
            barker.Bark();
        }
    }
}
  • 呼び出し元
public class Patroller {
    public void GoPatrol() {
        IBarkable bark = new Wolf();
        //見つかった人に、狼を連れてパトロールを命じる
        SecurityGuard guard = _guards.Find(何かしら条件);

        guard.Patrol(bark);
    }
}

オブジェクト

このとき、Patrolで受け取っているbarkerは、オブジェクトでしょうか?インスタンスでしょうか?

これは、オブジェクトです。*2
SecurityGuardは、IBarkableのオブジェクトを用いて、何かやっています。あくまで、利用しています。

外部から受け取ったものは、全てオブジェクトとして扱って差し支えありません。
SecurityGuardは、犬や狼といった実体は気にせず、「なんか吠える奴」を連れて、パトロールに行っています。

「なんか吠える奴」は、「吠える機能を持ったオブジェクト」です。

barker変数には、実体となるインスタンスが紐づいてはいますが、それはSecurityGuardからはどうでもよいことです。

インスタンス

オブジェクト指向言語においては、多くの場合クラスと呼ばれるものを元に作成したオブジェクトの実体を指す。

出典: インスタンス - Wikipedia

と、Wikipediaでは説明では説明されています。
はい、その通りです。

IBarkable bark = new Wolf();

これは、barkという変数(オブジェクト)に、Wolfクラスのインスタンスを作成し、その参照をセットする、という処理です。
ただ、インスタンスを作成した人*3にとって、bark変数は、「オブジェクトでありインスタンスである」という扱いになります。

はい、面倒。その通りです。

インスタンスを作った人の役割

ガベージコレクションが無いものとして話します。

インスタンスを作る、ということは、メモリ上にその実体を用意する、ということです。
実体を用意する以上、不要になったら、破棄する必要があります。

それとは別に、メモリ上の実体を見るために、参照という仕組みが存在します。

bark変数(オブジェクト)は、Wolfインスタンスへの参照を保持している、と言えます。

たとえば、以下のようにした場合、Wolfインスタンスはどこに行くでしょう?

public class Patroller {
    public void GoPatrol() {
        IBarkable bark = new Wolf();
        //見つかった人に、狼を連れてパトロールを命じる
        SecurityGuard guard = _guards.Find(何かしら条件);

        guard.Patrol(bark);

        bark = null;
    }
}

こうすると、最初に作ったWolfインスタンスは、「どこからも見えない」状態となります。*4
そのため、見えなくなる前に、Wolfインスタンス自体を「メモリ上から無くしてしまう」必要が出てきます。

ガベージコレクションが無い場合、メモリ解放専用の構文やメソッドが用意されているため、それを呼びます。
もしも、C#にその構文があるとしたら、こんな感じです。

public class Patroller {
    public void GoPatrol() {
        IBarkable bark = new Wolf();
        //見つかった人に、狼を連れてパトロールを命じる
        SecurityGuard guard = _guards.Find(何かしら条件);

        guard.Patrol(bark);

        //メモリ破棄っぽい構文(Delphiチックに)
        bark.Free();

        bark = null;
    }
}

基本的には、インスタンスの存在を確実に意識できるのは、作成者のみです。
仮に、渡した先で勝手にインスタンスを破棄(メモリを解放)されたら、たまったものではありません。
また、利用者からしても、いちいち「この参照のメモリは解放するのか?」などと、考えていられません。

よって、インスタンスは「作った人が破棄まで面倒を見るべき」と言えます。

ガベージコレクションについて

しかし、そうは言っても、システムが複雑になればなるほど、オブジェクトは複雑に絡み合い、破棄し忘れが発生します。
そこで生まれたのが、ガベージコレクションです。*5

略称として、GCがよく使われます。
C#では、そのまんまのクラスがあります。

細かい仕組みは省いて、ものすごくざっくり言うと、使わなくなったインスタンスを判別して、自動で解放してくれる機能です。

上記で、「インスタンスを作った人は破棄までやる」という話をしましたが、代わりにその破棄をやってくれます。

仕組み

簡単に言うと、

メモリ上にある、誰からも参照されていないインスタンスを探し出し、解放する

ように動いています。

ただ、この誰からも参照されていないの判定が、言語やOSによって違います。

言語 発動タイミング
C# メモリが足りなくなりそう等、いくつかのタイミングで発動
Android C#に似ているらしい
iOS 参照カウンタが0になったインスタンスについて発動

参照カウンタについては、興味があれば調べてみてください。

C#Javaをやっている人、そして、ガベージコレクション搭載の言語を使っている人は、このおかげでインスタンスの存在をそれほど気にせず、プログラミングできている、と思います。

ガベージコレクションのせいで、プログラムが遅くなる、という話もありますが、またそれは別の話…

まとめ

オブジェクトの例として、わざわざインターフェイスを使ったのは、インターフェイスインスタンスを作成できないからです。

とにかく、「オブジェクト ≠ インスタンス」である、という点を、利用者目線で強調してみました。

合わせて、ガベージコレクションの話もしましたが、これはまあ、あまり無駄なインスタンスは作らないように、という話です。
プログラム上、ほぼ不具合は出ないですが、無駄にインスタンスを作り続けると、最悪性能問題に発展します。

あと、私はメモリ管理についてC#ガベージコレクションあり)とDelphiガベージコレクションなし)で学びました。
このおかげで、メモリ管理についてある程度考えられるようになり、多言語への応用も利くようになりました。

プログラミングに慣れてきた人は、こういう部分を学ぶもの楽しいと思います。

ではでは。

*1:明示的に解放が必要な言語は、常に考え続けなければならないため、除く

*2:正確には、barker変数は、IBarkableオブジェクトへの参照

*3:Patrollerのこと

*4:この「どこからも見えない」状態になったインスタンスが、「メモリリーク」となる

*5:生まれについては調べていません。ごめんなさい。