公開:2019.10.22 19:50:00 更新:2019.11.10 01:48:43 Vala

Valaのメモリ管理 (参照)

Valaはオブジェクト指向プログラミング言語のひとつで、LinuxなどのUnix系・UnixライクなOSで使われている代表的なデスクトップ環境であるGNOMEを構成するGTK+ツールキットを使ったGUIアプリケーションの記述を簡単にできるように設計されています。
もちろんGTK+はクロスプラットフォームなのでGNOME以外の環境でも使うことができます。
今回はVala言語の仕様の中でも独特で少し難解なメモリ管理方法についてまとめました。

Valaのクラスと参照カウント

Valaではメモリを「参照カウント」によって管理しています。
Valaソースでクラスのインスタンスを生成した時には以下のような処理が行われます。

  1. クラスからインスタンスを生成する。
  2. そのインスタンスの参照カウントを1増やす。
  3. インスタンスへの参照を変数に代入する。
  4. 変数が使われなくなったら参照カウントを1減らす。
  5. 参照カウントが0になった場合、そのインスタンスを削除する。

同じインスタンスへの参照を持つ変数は何個でも作ることができます。
変数から変数へインスタンスへの参照を渡した際にも参照カウントが1増えます。

以下に、Dogクラスを生成するValaのソースと、そこから変換されたC言語のコードを比較します。

ValaC言語(要約です)
// インスタンスを生成する。
Dog pochi = new Dog();




// 別の変数に参照を渡す
Dog maruo = pochi;




// Dogのwanメソッド (ワンと鳴く)
// を実行
maruo.wan();













// 終わり
return;
// インスタンスを生成する。
Dog *pochi = dog_new();

// 参照カウントを1で初期化する。
pochi->ref_count = 1;

// 別の変数に参照を渡す。
Dog *maruo = pochi;

// 参照カウントを1増やす。
maruo->ref_count++;

// Dogのwanメソッド (ワンと鳴く)
// を実行
dog_wan(maruo);

// 参照カウントを1減らす
poshi->ref_count--;

// 参照カウントを1減らす
maruo->ref_count--;

// 参照カウントが0に到達したら
// インスタンスを解放する。
if (maruo->ref_count == 0) {
    free(maruo);
}

// 終わり
return;

どうでしょうか。Vala言語はC言語で行われているメモリ管理の詳細をかなり隠蔽できていることが見てとれると思います。
ちなみに、ValaのソースからCのソースを出力するにはvalacコマンドに-Cオプションを付けて実行します。

 valac -C [ファイル名]

弱い参照

弱い参照 (weak reference) は参照を変数から変数に渡す時に参照カウントを増減させません。
弱い参照には何種類かありますが、ここではweakunownedについてまとめます。

weakキーワード

weakキーワードとunownedキーワードは実のところ同じものですが、開発者によるとweakキーワードはリンクリストなどで発生する「循環参照」が引き起こす「メモリリーク」防止のためのものだと思って欲しいそうです。
この循環参照の問題はウィキペディアにも記事が出ていますので詳しく解説しませんが、要するにお互いを参照する変数がある時、片方にweakキーワードを付ける必要があるということです。
循環参照の問題点 | ウィキペディア
これは参照カウント方式の弱点を補うための仕様なので、循環参照構造自体なるべく使わないことが推奨されることになると思います。
私たちがValaを使って作りたいのはデスクトップアプリケーションなので、循環参照が生じるような複雑な構造を使いたいときはlibGeeのLinkedListを使うなどするのが安全簡単だと思われます。

class DoublyLinkedList<T> {
    private DoublyLinkedList next;
    private weak DoublyLinkedList prev;
    public T data { get; set; }

    public DoublyLinkedList(DoublLinkedList prev) {
        // 前の要素にとっての次の要素は参照カウンタを増やす
	prev.next = this;
	
	// この要素にとっての前の要素は参照カウンタを増やさない
        this.prev = prev;
    }
}
unownedキーワード

フィールドを持つオブジェクトはオブジェクトが削除されるのと同時にフィールドのオブジェクトも削除しようとしますが、フィールドを参照している別の変数がある場合、参照カウントが0にならないので削除されません。
オブジェクト削除のタイミングでフィールドも全て削除したい場合は、ゲッターメソッドにunownedキーワードを付けてフィールドへの参照を返します。

// 人クラス
class Human : Object {
    // タトゥー
    private Tatoo tatoo

    // 人のタトゥーへの参照
    public unowned Tatoo get_tatoo() {
    	   return tatoo;
    }
}

このように定義したクラスのインスタンスからtatoo (タトゥー) を参照したい場合

unowned Tatoo his_tatoo = john.get_tatoo();

というようにします。
ローカル変数his_tatoounownedなので、john.tatooの参照カウントが増減しません。
こうするとジョンが消えるのと同時にタトゥーも消えます。
ジョンが消えたのにタトゥーだけ残っていると変ですからね。

逆にジョンが消えてもタトゥーだけ残したい場合はunownedを付けないようにします。

はっきり言ってここら辺ややこしいので、上に挙げたような例以外では使わず、オブジェクトの管理は参照カウント機能に任せた方が良いでしょうね。プログラミングしている時は忙しいのでこのような細かいことは気にしていられません。

プロパティ

Valaのクラスはフィールドとは別にプロパティというものを設定できますが、これはデフォルトで弱い参照になります。

// 人クラス
class Human : Object {
    // 口 (プロパティ)
    public Mouth mouth { get; set; }

    // 耳 (プロパティ)
    public Ear ear { get; set; }

    public Human() {
        mouth = new Mouth();
	ear = new Ear();
    }
}

このようなクラスがある時、人の口や耳はプロパティとしてフィールドのように参照できますが、弱参照なので外部から参照する変数は参照カウントを増減させません。
そのため、耳や口は持ち主の人が消えるのと同時に消えます。

ポインタ

ValaではC言語のようなポインタを使うこともできます。
ポインタにインスタンスへの参照を代入。

Dog *dog = new Dog();

ポインタからインスタンスのメソッドを実行

dog->wan();

削除する時

delete dog;

変数から変数へ参照を渡す時

Dog dog = new Dog();
Dog *dog_ptr = dog;

上の例2行目はunownedの場合と同じく、参照カウントは増減しません。
そのため、2番目以降のポインタに対してdeleteをする必要はありません (すると実行時エラーになります)。

ポインタは自分でコーディングをする際には使う必要はありません。
必要になるのはC言語で書かれたライブラリを使う時です。
例えばlibxml2のノードオブジェクトはGTKのオブジェクトではないのでポインタで扱います。
しかし、それもVala向けのバインディングがあれば必要なくなります。

参照渡し

関数やメソッドに変数の参照を渡すと、引数を戻り値として使えるようになります。
参照渡しにはoutキーワードかrefキーワードを付けます。

outキーワード

outを付けて渡した変数はメソッド内部で初期化されます。
戻り値として使うことができます。
C言語で言えばscanfなどの引数と同じイメージです。

よくあるケースとしては

  • 戻り値が複数ある場合
  • 戻り値が構造体 (ヒープにない) の場合

という場合に使われます。

// RGB色の構造体
struct Color {
    public int red;
    public int green;
    public int blue;
}

// 色に白を設定する関数
void set_white(out Color color) {
    color.red = 255;
    color.green = 255;
    color.blue = 255;
}

// 呼び出し側
void main() {
    Color color;
    set_white(out color); // => colorに{255,255,255}が設定されている
}
refキーワード

refを付けて渡す変数は初期化済みである必要があります。
よくあるケースとしては

  • リストやツリー構造のイテレータを更新する

という場合に使います。

// 曲名がプレイリストにあるか確認するメソッド
bool music_exists(string name) {
    Gtk.TreeStore store = (Gtk.TreeStore) playlist_tree.model;
    if (store.iter_has_child(playlist_root)) {
        Gtk.TreeIter iter;

	// ↓イテレータを初期化する為にoutを使っている。
        store.iter_children(out iter, playlist_root);
        do {
	    // プレイリストの中を検索する処理
            GLib.Value val;

	    // ↓Valueは構造体なのでoutで結果を受け取る
            store.get_value(iter, 1, out val);
            string val_name = (string) val;

	    // ↓曲名が見つかったらtrueを返す
            if (val_name == name) {
                return true;
            }
        } while (store.iter_next(ref iter));
	// ↑ イテレータの更新にrefを使っている
    }
    return false;
}

これもややこしいのですが、APIがrefoutを要求している場合は使うしかありません。
自分で作る時もどうしても必要という時以外、積極的に使うものではないですね。

この機能はC#を真似したものっぽいです。
C#の記事によくまとまっているものがありました。
C# out と ref @muro | Qiita 2016.08.16

まとめ

いかがでしたか。
ややこしい話になってしまいましたが、基本的に例外的規則になるのであまり使うことはない弱参照についての話がほとんどになりました。まとめるとこのようになります (※個人の感想です)。

  1. weak: オブジェクトは基本的にツリー構造にすることで循環参照が発生しないように気を付けます。
  2. unowned: オブジェクトの持ち主から離れないようにしたい時にはunownedを使います。(ほとんどないですが)
  3. プロパティ: オブジェクトの設定値などはプロパティを使います。(これが最も使います。)
  4. ポインタ: C言語のライブラリを呼び出す時に使います。
  5. out引数: 戻り値の代わりに使います。(構造体に値を設定する場合だけですが)
  6. ref引数: イテレータの更新で使う場合があります。

以上になります。