glib: 移植性と実用性
glib は、UNIXシステム互換機及びウインドウズ上で Cの移植性ある実用的ライブラリです。
この章では GTK+ や Gnome アプリケーションで最も標準的に使われているライブラリの特徴を
いくつか解説します。
glib は単純で、概念的にもよく知られています。ですから、ここでは簡単にすませましょう。
glib のより詳しい説明は、glib.h
またはライブラリに付属するフリー glibリファレンスマニュアルをご覧下さい。
(話ははずれますが、glib、GTK+、もしくは Gnomeヘッダファイルの利用に関しては
躊躇しないで下さい。それらは非常に簡潔で読みやすくクイックリファレンスとして便利です。
関連して、もし実装について非常に特殊な疑問があるようでしたら、
ぜひソースコードを見て下さい。)
glib の様々な機能は、一貫性のあるインタフェースになることを意図しています。例えば、コーディング
形式は半オブジェクト指向で、識別子は一連のネームスペース作成のため "g" を先頭に
伴っています。
glib には、シングルヘッダファイル glib.h
が存在します。
基本
glib は、標準的によく使われる C言語構文の置換を行います。
この章では、glibの
基礎的なタイプ定義、マクロ、メモリ割り当てルーチン、ユーティリティ関数の配列
について解説します。
タイプ定義
glib は、Cの標準タイプ (int,
long, etc.) を用いるのではなく、
それ独自の定義を行います。それらは、様々な用途に適しています。
例えば、gint32 は、標準の Cタイプでは
確実にできない、32ビット幅を保証できます。
guint は、単純ですが unsigned よりタイプにするのが容易です。
整合性のみを考えた typedef (タイプ定義)は少数ですが存在します。
例えば、gcharは、常時通常の
char と同等です。
以下の従来的タイプは glib で定義されています。
gint8, guint8, gint16, guint16, gint32, guint32, gint64, guint64—これらは保証サイズの整数値を
表しています。全てのプラットフォームが、64ビットの整数を提供するという
わけではありません。つまりプラットフォームが、それらを必要とするならば、
glib は、G_HAVE_GINT64 を定義
しなければなりません。(簡単にいうと、
guint タイプは unsigned で、
gintタイプは signed です。)
C では boolタイプが存在しないので、
gboolean はコードを更に読みやすく
させるのに役立ちます。
gchar, gshort, glong, gint,
gfloat, gdouble は、全くの装飾です。
gpointer は、
void * よりもタイプに対して都合が
いいかもしれません。gconstpointer
は、const void* を与えます。
(const gpointer は、概して
予想される処理を行いません。理由が明確でないよう
でしたら、Cの優れた参考書をご覧下さい。)
よく使われるマクロ
glib は、多くの Cプログラムで用いられる数々の一般的なマクロを定義しています。
で示されています。
これら全てが自明であるべきです。
MIN()/MAX()
は、その引数の中で最小値及び最大値を返します。
ABS() は、その引数の絶対値を返します。
CLAMP(x, low,high) は、
x が
[low,high] の範囲内ならば x を意味します。
つまり、x が範囲より小さければ
low を返し、
x が範囲より大きければ
high を返します。
下記に示すマクロに加え、
TRUE/FALSE/NULL は、通常通り 1/0/((void*)0)と定義されています。
よく使われる C のマクロ
#include <glib.h>
MAX
a
b
MIN
a
b
ABS
x
CLAMP
x
low
high
glib に特有な多くのマクロも存在します。例えば、下記 のような移植可能な gpointer-to-gint や gpointer-to-guint の変換です。
多くの glib のデータ構造は、gpointer
に格納されるようになっています。
動的に割り当てられたオブジェクトにポインター
を格納したいというのは正当です。しかしながら、
時には動的に割り当てる必要なく、
単純な整数列の格納を要することもあるでしょう。
Cの規格ではそれを厳密に保証していませんが、
gint
もしくは guint を
gpointer変数への格納は
glib が組み込まれたプラットフォームといった幅広い範囲で可能です。
もちろん場合によっては、それに対するなんらかの媒体が必要になるでしょう。
下記 のマクロでは、
仲介媒体の表記を表しています。
具体例です。
gint my_int;
gpointer my_pointer;
my_int = 5;
my_pointer = GINT_TO_POINTER(my_int);
printf("We are storing %d\n", GPOINTER_TO_INT(my_pointer));
注意点は、確かにこれらのマクロはポインタで整数の格納を可能にしますが、
整数でポインタを格納することはできません。
このような移植を行うには、ポインタを
long に格納しなければなりません。
(そうすることは、間違いなくよくないことなのですが。)
ポインタで整数を格納するためのマクロ
#include <glib.h>
GINT_TO_POINTER
p
GPOINTER_TO_INT
p
GUINT_TO_POINTER
p
GPOINTER_TO_UINT
p
デバッグのマクロ
glib には、コードで不変値や選定条件を実装できるような便利なマクロが存在します。
GTK+ は、これらのライブラリを用いています。—理由の一つは、
非常に安定していて使いやすいからです。それら全てが、
G_DISABLE_CHECKS もしくは
G_DISABLE_ASSERT を定義すると
消滅します。つまり、できたコード上のパフォーマンスの低下はありません。
これらのライブラリを用いることは、非常に良い考えです。これを使うと、
より速くバグを見つけられるでしょう。以後のバージョンで再現性のないバグを
発覚したときはどんな場合でも、アサーションマクロ(assertion)
及びチェックマクロ(check)を加えることさえ可能です。
—これは、回帰的組合せを補充します。
チェックは、特に書いているコードが、ほかのプログラマにブラックボックスとして
扱われる場合、特に役立ちます。つまりユーザは、どんなときどのようにそのコードを
使い間違えたか直ちに分かるのです。
もちろん、正確的に機能に対してデバッグのみのステートメントに
わずかながらも依存していないと確実性をもてるよう注意しなければ
なりません。生産コードで消滅するステートメントは、
副作用を起こしてはいけません。
前提条件のチェック
#include <glib.h>
g_return_if_fail
condition
g_return_val_if_fail
conditionretval
は、glibの
前提条件チェックを示しています。
g_return_if_fail()
は、警告を出力し、conditionが
偽(FALSE)ならば直ちに現行の関数から
戻ります。
g_return_val_if_fail() は、
似ていますが、retvalを返すことが
できます。
これらのマクロは、驚くほど便利です。—頻繁に用いれば、
とりわけGTK+の実行時タイプチェックと併せることで、
不正なポインタやタイプエラーを発見するのに費やす時間を半減する
でしょう。
これらの関数を用いるのは簡単です。
これは、glibのハッシュテーブルの実装例です。
void
g_hash_table_foreach (GHashTable *hash_table,
GHFunc func,
gpointer user_data)
{
GHashNode *node;
gint i;
g_return_if_fail (hash_table != NULL);
g_return_if_fail (func != NULL);
for (i = 0; i < hash_table->size; i++)
for (node = hash_table->nodes[i]; node; node = node->next)
(* func) (node->key, node->value, user_data);
}
チェックを省き、パラメータとしてNULL
をこの関数に渡すと不可思議なセグメンテーションフォールトといった
結果になるでしょう。
ライブラリを用いている場合は、どこでエラーが起こったかデバッガを
使ってはっきりさせなければならないでしょう。
何が悪いのかを見つけるには、glibのコードまで掘り下げないと
いけないかもしれません。
チェックを使うとNULL引数が
認められていないと伝わり、正当なエラーメッセージが出されます。
アサーション
#include <glib.h>
g_assert
condition
g_assert_not_reached
glibは、更に従来のアサーションマクロも用意してあります。
をご覧下さい。
g_assert() は、
基本的に assert() と同一ですが、
G_DISABLE_ASSERT に応答し、
どのプラットフォームでも一貫性をもって稼働します。
g_assert_not_reached()も提供されて
おり、これは常に失敗するアサーションです。
アサーションは、プログラムを終了するため
abort() をコールし、
(ご自分の環境がサポートしているならば)
デバッグの役割としてコアファイルを作り出します。
フェイタルアサーションは、関数及びライブラリの
内部整合性をチェックするのに用いられるべきです。
g_return_if_fail()は、
正しい値がプログラムモジュールの公的インタフェースに確実に
渡されることを意図されています。
つまり、アサーションが失敗したら概してアサーションを含むモジュール上
の不具合を探します。
g_return_if_fail()が、失敗を
チェックすると、大概モジュールを起動するコード上で不具合を
探すでしょう。
glibのカレンダ計算モジュールではこのコードは、相違点を表しています。
GDate*
g_date_new_dmy (GDateDay day, GDateMonth m, GDateYear y)
{
GDate *d;
g_return_val_if_fail (g_date_valid_dmy (day, m, y), NULL);
d = g_new (GDate, 1);
d->julian = FALSE;
d->dmy = TRUE;
d->month = m;
d->day = day;
d->year = y;
g_assert (g_date_valid (d));
return d;
}
始まりの前提条件チェックは、日、月、年に対して適した値にユーザが
渡しているかを保証します。終わりのアサーションは、glibが正しい
オブジェクト、つまり与えられた正しい値を構成したとの確証です。
g_assert_not_reached() は、
"不可能な"状況を印すのに用いられるべきです。
共通の用途は、列挙法の全て可能な値を処理するわけではない
switch文を発見する点です。
switch (val)
{
case FOO_ONE:
break;
case FOO_TWO:
break;
default:
/* 不当な列挙法値 */
g_assert_not_reached();
break;
}
デバッグ用のマクロ全てが、glibのg_log()機能を
用いて警告を出力します。
つまり警告は、元来のアプリケーション名及びライブラリを含み、
それゆえ任意に警告-出力ルーチンの置換を備えられるのです。
例えば、全警告をコンソール上で出力する代わりにダイアログボックス、
またはログファイルに残すことも可能です。
メモリ
glib は、標準malloc()と
free()を独自のg_
変形を加え、g_malloc()及び
g_free()の形で含んでいます。
をご覧下さい。
これらは、数々の小さな点で優れています。
g_malloc() は、いつも gpointerを返し、決してchar*を返しません。
つまり、戻り値をキャストする必要性がないのです。
g_malloc() は、malloc()
の基礎的な部分が失敗していたら異常終了します。
それゆえ、NULL戻り値を
チェックする必要はありません。
g_free()は、
NULLを返すことで
0といった
sizeを上手く処理します。
g_free() は、渡したどのような NULL ポインタをも無視します。
このちょっとした利点に加え、g_malloc()と
g_free()は、様々な種類のメモリデバッギングや
プロファイリングをサポートできます。
--enable-mem-checkオプションを
glibの設定スクリプトに渡すと、コンパイルされた
g_free()は、同じポインタを解放する際、
警告を与えるでしょう。
--enable-mem-profileオプションは、
メモリの使用統計を保つコードを有効にします。
つまり、g_mem_profile()をコールすると
コンソール上に出力されます。
最終的には、USE_DMALLOCを定義でき、
glibのメモリ確保には、MALLOC()
等を使用します。デバッギングマクロは、同プラットフォーム上
dmalloc.hで有効です。
glib メモリ割り当て
#include <glib.h>
gpointer g_malloc
gulong
size
void g_free
gpointer mem
gpointer g_realloc
gpointer
memgulong
size
gpointer g_memdup
gconstpointer
memguint
bytesize
g_malloc()にg_free()が
通常のmalloc()にはfree()が
対応していることが重要です。(もし C++ をお使いなら)
newに対して
deleteです。
さもなければ、最悪の事態が生じるでしょう。というのも、
この割り当てが異なるメモリプールを使用するかもしれないからです。
(そして、new/
deleteは、構成関数と消去関数を
コールします。)
当然g_realloc()は、realloc()
と同等です。更に便利なg_malloc0()も存在し、
それによって配置されたメモリは、ゼロクリアされます。
g_memdup()は、
memで始まっているbytesizeバイトのコピーを返します。
g_realloc()とg_malloc0()は、
g_malloc()との整合性のため、
共に0といったsizeを受け入れます。
しかしながら、g_memdup()は、受け入れません。
次のこと: g_malloc0()は、ビットすら設定
されていないメモリを扱っているということ
が明確でないならば、そこに配置しようとしているどのようなタイプに対して
値0ではなくなります。
時に、0.0に初期化された浮動小数点
の配列が得られると予測する人がいますが、このようには動作
しません。
最後に、型-認識の割り当てマクロもあります。
をご覧下さい。
これら各々へのtype引数は型名で、
count引数は、
割り当てのtype-サイズブロック数です。
これらのマクロは、多少のタイプ入力や増殖の余地を残してくれるので、
エラーの生じる頻度が減ります。
また、自動的にターゲットポインタ型にキャストするので、
そこで割り当てられたメモリを誤った種類のポインタに当てようとすると
コンパイラ警告をトリガすべきでしょう。
(これは、警告を出すようにしてあればですが、信頼性のあるプログラマは
そうするでしょう!)
割り当てマクロ
#include <glib.h>
g_new
typecount
g_new0
typecount
g_renew
typememcount
文字列処理
glib は、文字列処理用の関数を多数提供しています。
中にはglib特有なものもや移植性への問題を解決するものもあります。
これらは全てglibのメモリ割り当てルーチンで、上手く内的操作を
行っています。
さらに、gchar*より優れた文字列に
焦点を当てるとGStringタイプも存在
します。本書は、それに関しては対象としていませんがドキュメントは
http://www.gtk.org/から得られます。
移植性のラッパ
#include <glib.h>
gint g_snprintf
gchar*
bufgulong
nconst gchar*
format
...
gint g_strcasecmp
const gchar*
s1const gchar*
s2
gint g_strncasecmp
const gchar*
s1const gchar*
s2guint
n
は、glibが一般に実装している
置換の代表です。しかしながら、ANSI Cへの拡張移植は可能ではありません。
C の面倒な点の一つは、クラッシュの原因、セキュリティホール発生といった
通常好ましくないsprintf()を供給することですが、
比較的安全で広く実装されているsnprintf()は、
ベンダー拡張の一つです。
g_snprintf()は、
本来のsnprintf()の機能もそれを伴っている
プラットフォーム上では含んでおり、伴っていないプラットフォームでは
実装します。
そのため絶対的にsprintf()を使用しないことも
可能です。 多少よくなっているといえても、
従来的にsnprintf()は、そのバッファを
NULL終了する保証はありません。
g_snprintf()は、確実です。
g_strcasecmp() と
g_strncasecmp()は、 2つの文字列の大文字、
小文字を区別せず実行します。任意で最大長を用いることもできます。
strcasecmp() は、
多数のプラットフォームで使用可能ですが、一般的ではありません。
代わりに、glibを用いるのは賢明です。
の関数は、文字列を適当に修正
します。はじめの2つは、それぞれ文字列を小文字及び大文字に変更し、
g_strreverse()は、その文字の大きさを逆にします。
g_strchug()とg_strchomp()は、
文字列を"chug"(先置スペースを削除)し、
一方で"chomp"(後置スペースを削除)します。
これら最後の2つは、文字列を適当に修正しつつ返します。
場合によっては、戻り値を用いる方が便利なこともあるかもしれません。
マクロも存在します。g_strstrip()は、
先置、後置スペースを共に取り除く機能を兼ね併せていますが、
単に個々の機能として用いられます。
適した文字列の修正
#include <glib.h>
void g_strdown
gchar*
string
void g_strup
gchar*
string
void g_strreverse
gchar*
string
gchar* g_strchug
gchar*
string
gchar* g_strchomp
gchar*
string
は、glib が補っている
いくつかの半標準関数を示しています。g_strtodは、
ユーザのデフォルトロケールで変換に失敗した場合
"C"ロケールでダブルに
変換を行なおうとする例外はありますが、
strtod()—
文字列nptrをダブルに変換
—のような感じです。
*endptrは、最初の非変換文字、
つまり数値形式後のどんなテキストにでも設定されます。
変換に失敗した場合*endptrは、
nptr に設定されます。
endptr は、
無視されるようにさせるため、NULL
のこともあります。
g_strerror() と g_strsignal()
は、その拡張子g_ がない関数
と類似しますが、移植可能です。(それらは、errnoとして文字列形式、またはシグナルナンバーを
返します。)
文字列変換
#include <glib.h>
gdouble g_strtod
const gchar*
nptrgchar**
endptr
gchar* g_strerror
gint
errnum
gchar* g_strsignal
gint
signum
は、文字列割り当てに関する
glibの豊富な機能の配列です。驚くようなことではありませんが、
g_strdup() と g_strndup()
は、割り当てられたstrのコピー、
もしくは最初のstrの
n文字を生成します。
整合性のためglibメモリ割り当て関数を用いると、
NULLポインタが渡されると
NULLを返します。
printf()の変形は、フォーマットされた文字列を
返します。
g_strescape は、その引数であらゆる
\文字をそれらの前に別の
\を挿入することでエスケープし、
エスケープ文字列を返します。
g_strnfill()は、
fill_charで埋め込まれた
サイズlengthの文字列を返します。
g_strdup_printf()には、特に述べておくべき点
があります。それは、コードのこの共通部分を処理するのにより容易な方法
ということです。
gchar* str = g_malloc(256);
g_snprintf(str, 256, "%d printf-style %s", 1, "format");
このように宣言する代わりに、ブートにバッファの正確な長さを換算する
必要性がなくなります。
gchar* str = g_strdup_printf("%d printf-style %s", 1, "format");
文字列の割り当て
#include <glib.h>
gchar* g_strdup
const gchar*
str
gchar* g_strndup
const gchar*
formatguint
n
gchar* g_strdup_printf
const gchar*
format
...
gchar* g_strdup_vprintf
const gchar*
formatva_list
args
gchar* g_strescape
gchar*
string
gchar* g_strnfill
guint
lengthgchar
fill_char
glib は、文字列を連結するのに適した関数を提供します。
をご覧ください。
g_strconcat()は、引数に並んでいる
それぞれの文字列を連結することで作成される新しく割り当てられた文字列
を返します。最後の引数は、NULL
でなければなりません。そこで、g_strconcat()は、
いつ停止すべきかを知るのです。
g_strjoin()は似ていますが,
separatorがそれぞれの文字列間に
挿入されます。separatorが、
NULLの場合は、separatorは
用いられません。
文字列の連結
#include <glib.h>
gchar* g_strconcat
const gchar*
string1
...
gchar* g_strjoin
const gchar*
separator
...
最後に、は、
NULL-文字列の最終配列を
処理するルーチンです。g_strsplit()は、
各々のdelimiterで
stringを中断し、新たに割り当てられた
配列を返します。g_strjoinv()は、
任意にseparatorを用いて、
配列にそれぞれの文字列を連結し、割り当てられた文字列を返します。
g_strfreev()は、配列でそれぞれの文字列を
解放し、それゆえ配列自身も解放します。
処理方法 NULL-最終文字列ベクトル
#include <glib.h>
gchar** g_strsplit
const gchar*
stringconst gchar*
delimitergint
max_tokens
gchar* g_strjoinv
const gchar*
separatorgchar**
str_array
void g_strfreev
gchar**
str_array
データ構造
glib は、共通のデータ構造を多数実装しているので、リンクリストを
必要とするたびごとに、適した展開方法を再開発する必要はありません。
この章は、glibのリンクリスト、ソートされたバイナリ系ツリー、
多進系ツリー、そしてハッシュテーブルの実装についてです。
リスト
glib は、通常のシングルおよびダブルリンクリストを、それぞれ
GSList と GListで提供します。
これらは、gpointerのリストとして
実装されています。
つまり、GINT_TO_POINTER及び
GPOINTER_TO_INTマクロを用いて、
整数を確保することができます。
GSList と GList は、g_list_previous()
関数は存在し、g_slist_previous()は存在しない
といった例外はありますが、同一のAPIです。
この章ではGSListについて記述して
いますが、すべてダブルリンクリストにも適用します。
glibの実装では、空のリストは単なる
NULLポインタです。それは、長さ0の
有効なリストなので、常時安全にNULL
をリスト関数に渡します。リストを作成し、一つの要素を追加するコードは、
次のようになります。
GSList* list = NULL;
gchar* element = g_strdup("a string");
list = g_slist_append(list, element);
glib リストは、Lisp の影響を強く受けています。つまり空のリストは、
それが理由で特別に"nil"値になります。
g_slist_prepend()は、
cons—新しいセルをリスト
の前に追加する定期時刻操作のように頻繁に動作します。
注意すべき点は、リストのヘッドが変った場合、戻り値を使ってリストを修正
する関数に渡されるリストに置き換えなければならないということです。
glibは、必要に応じてリストリンクの割り当て解除や割り当てをしながら
メモリの問題を処理します。
例えば、以下のコードは上記に追加された要素を取り除き、
リストを空にします。
list = g_slist_remove(list, element);
list は、現在NULLです。
もちろん、まだ自分自身でelement
を解放しなければなりません。
全てのリストをクリアするには、g_slist_free()を
使用してください。これは、一度に全てのリンクを取り除きます。
g_slist_free()は、戻り値を取りません。というのは、
常にNULLだからです。それゆえ、
好みによって簡単にその値をリストに割り当てられます。
明らかにg_slist_free()は、リストのセルだけを
解放するのであって、そのリストコンテンツでどのような処理が行なわれるか
知る手法はありません。
リストの要素にアクセスするには、
直接GSList struct に明記します。
gchar* my_data = list->data;
リストの繰返しには、コードを次のように書くといいでしょう。
GSList* tmp = list;
while (tmp != NULL)
{
printf("List data: %p\n", tmp->data);
tmp = g_slist_next(tmp);
}
は、
GSListコンテンツを変更するための
基本的な関数を示しています。
これら全てに対して、リストのヘッド変更時にはリストポインタに戻り値を
割り当てなければなりません。注意すべき点は、glibはリストの最後に
ポインタを格納しません。
そのためフリペンドは、定期時刻操作であり、追加、挿入、削除
は、リストのサイズに比例します。
とりわけ、これはg_slist_append()を用いてリストを
作成することがとんでもない考えであることを
意味します。もし、特別な順序でアイティムが必要ならば、
g_slist_prepend()を用いて、その後
g_slist_reverse()を呼び出します。
リストに、頻繁に追加する可能性があるならば、最後の要素をポインタに
しておくこともできます。以下のコードは、効率的に追加を実行する
のに用いられています。
void
efficient_append(GSList** list, GSList** list_end, gpointer data)
{
g_return_if_fail(list != NULL);
g_return_if_fail(list_end != NULL);
if (*list == NULL)
{
g_assert(*list_end == NULL);
*list = g_slist_append(*list, data);
*list_end = *list;
}
else
{
*list_end = g_slist_append(*list_end, data)->next;
}
}
この関数を用いるには、リストとその後尾をどこかに格納し、それらの
アドレスをefficient_append()に渡します。
GSList* list = NULL;
GSList* list_end = NULL;
efficient_append(&list, &list_end, g_strdup("Foo"));
efficient_append(&list, &list_end, g_strdup("Bar"));
efficient_append(&list, &list_end, g_strdup("Baz"));
もちろん、list_endをアップデート
せずにリストの最後を変更する可能性のあるリスト関数を使用しないよう
注意しなければなりません。
リンクリストコンテンツの変更
#include <glib.h>
GSList* g_slist_append
GSList*
listgpointer
data
GSList* g_slist_prepend
GSList*
listgpointer
data
GSList* g_slist_insert
GSList*
listgpointer
datagint
position
GSList* g_slist_remove
GSList*
listgpointer
data
リスト要素にアクセスするために、
の関数が提供されます。
これら全てが、リストの構造を変更しません。
g_slist_foreach()は、
GFuncをリストのそれぞれの要素に
あてはめます。GFuncは、次のように
定義されています。
typedef void (*GFunc)(gpointer data, gpointer user_data);
g_slist_foreach()で用いられると、GFuncは、list
内の各list->dataでコールされ、
g_slist_foreach()に提供されたuser_dataを渡します。
g_slist_foreach()は、Scheme の "map" 関数に
相当します。
例えば、文字列のリストがあり、その文字列に応じた変換で並列リストを
作成可能にする必要があるかもしれません。
これらは、比較的単純な例からの引用で
efficient_append()関数を用いているコードです。
typedef struct _AppendContext AppendContext;
struct _AppendContext {
GSList* list;
GSList* list_end;
const gchar* append;
};
static void
append_foreach(gpointer data, gpointer user_data)
{
AppendContext* ac = (AppendContext*) user_data;
gchar* oldstring = (gchar*) data;
efficient_append(&ac->list, &ac->list_end,
g_strconcat(oldstring, ac->append, NULL));
}
GSList*
copy_with_append(GSList* list_of_strings, const gchar* append)
{
AppendContext ac;
ac.list = NULL;
ac.list_end = NULL;
ac.append = append;
g_slist_foreach(list_of_strings, append_foreach, &ac);
return ac.list;
}
glib 及び GTK+ は、"関数ポインタやユーザデータ" の用語を
多量に使用します。
関数プログラミング的な経験があるようでしたら、
これがクロージャを作成するためラムダ式を使用する
ような感じです。(クロージャは、関数を環境
と組み合わせます—名前-拘束値のセットにします。この場合、"環境"
は、append_foreach()に渡すユーザデータで、
"クロージャ"は、関数ポインタとユーザデータの組合せです。)
リンクリストでデータにアクセス
#include <glib.h>
GSList* g_slist_find
GSList*
listgpointer
data
GSList* g_slist_nth
GSList*
listguint
n
gpointer g_slist_nth_data
GSList*
listguint
n
GSList* g_slist_last
GSList*
list
gint g_slist_index
GSList*
listgpointer
data
void g_slist_foreach
GSList*
listGFunc
funcgpointer
user_data
手軽なリスト-操作のルーチンも存在し、
に示されています。
g_slist_copy()の例外を除き、これら全てがリストの
適した部分に反映します。つまり、戻り値を割り当て、ポインタに
渡された値についてはそのままにしておかなければならないということです。
リスト要素を追加または除去する時のようにです。
g_slist_copy()は、新たな割り当てリストを返すので、
両方のリストを継続して使えますが、結局は両リストを解放しなければ
ならないでしょう。
リンクリストの処理
#include <glib.h>
guint g_slist_length
GSList*
list
GSList* g_slist_concat
GSList*
list1GSList*
list2
GSList* g_slist_reverse
GSList*
list
GSList* g_slist_copy
GSList*
list
ソートリストのために
のような関数が備わっています。これらを使用するためには、
GCompareFuncを明記しなければ
なりません。これは、ちょうど標準Cの比較関数qsort()
に類似します。glibの形式では次のようになります。
typedef gint (*GCompareFunc) (gconstpointer a, gconstpointer b);
a < bの時、関数は負の数を返し、
a > bの時は正数値を、
a == bならば、0を返します。
比較関数を用いると、既にソートされているリストにある要素を挿入したり、
リスト全体をソートしたりできます。リストは、昇順にソートされます。
g_slist_find_custom()を用いると、
リストの要素を検索する際GCompareFunc
を再利用することができます。
(注意:glibではGCompareFuncは、
矛盾した用いられかたをされます。glibは、
時にqsort()-型関数の代わりになり、同等の動作を
期待します。しかしながら使用は、リストのAPI
範囲内で一貫性をもっています。)
ソートリストには注意が必要です。というのは、誤用すると急に
効率が悪くなるからです。
例えばg_slist_insert_sorted()は、
O(n)オペレーションですが、ループに多数要素を挿入させるために用いると
ループが爆発的な頻度で実行します。単純に全ての要素をプリペンドし、
g_slist_sort()を呼んだ方がいいでしょう。
ソートリスト
#include <glib.h>
GSList*
g_slist_insert_sorted
GSList*
listgpointer
dataGCompareFunc
func
GSList* g_slist_sort
GSList*
listGCompareFunc
func
GSList* g_slist_find_custom
GSList*
listgpointer
dataGCompareFunc
func
ツリー
glibには、2種類のツリーが存在します。
GTreeは、基本的なバランスのとれた
バイナリツリーで、キーによってソートされ、
キー-値を格納する際役立ちます。GNode
は、任意のツリー構造化されたデータ、例えば分析ツリーまたは分類を格納
します。
GTree
GTreeを作成かつ破棄を行なうには、
に示されているような
作成-削除の組合せを用います。
GCompareFuncは、
GSListに記述されている
qsort()-型比較関数と同様です。
この場合、ツリーでキーを比較するのに用いられます。
バランスバイナリツリーの
作成と破棄
#include <glib.h>
GTree* g_tree_new
GCompareFunc
key_compare_func
void g_tree_destroy
GTree*
tree
ツリーのコンテンツを処理するための関数は、
に示されています。
全てがシンプルです。g_tree_insert()は、
既に存在している全ての値を上書きします。存在している値が、
割り当てられたメモリへの唯一のポインタである時は注意してください。
g_tree_lookup()が、キー検索に失敗すると
NULLを返すか、関連値を返します。
キーも値も共にタイプgpointer
をもっていますが、GPOINTER_TO_INT()
やGPOINTER_TO_UINT()マクロは、
その代わりに整数を使用することもできます。
GTree コンテンツ処理
#include <glib.h>
void g_tree_insert
GTree*
treegpointer
keygpointer
value
void g_tree_remove
GTree*
treegpointer
key
gpointer g_tree_lookup
GTree*
treegpointer
key
ツリーがどれほどの大きさであるかといった概念をもたらす2つの関数が
に示されています。
GTree
のサイズ決定
#include <glib.h>
gint g_tree_nnodes
GTree*
tree
gint g_tree_height
GTree*
tree
g_tree_traverse() () を用いると、
ツリー全体をたどることができます。それを使用すると、
GTraverseFuncを提供し、それには
各々のキー-値ペアとg_tree_traverse()に与えた
data引数が渡されます。
GTraverseFuncが、
FALSEを返すと全探索は継続し、
TRUEが返されると全探索が終了
します。値を用いてツリーを検索する際にも使用できます。
これは、GTraverseFuncの定義です。
typedef gint (*GTraverseFunc)(gpointer key, gpointer value, gpointer data);
GTraverseType を列挙すると、
4つの可能な値があります。以下は、
GTreeに関する意味です。
まずG_IN_ORDER は、ノードの
左の子 (GCompareFuncに従う
"下の"キー)を見て、それから現ノードのキー-値の組に探索関数を
コールし、それから右の子を行ないます。この探索は、低い方から
高い方へとGCompareFuncに
従って順次行なわれます。
G_PRE_ORDER は、現ノード
のキー-値の組に探索関数をコールし、それから左の子に移り、
右の子へと移動します。
G_POST_ORDERは、左の子に移り
それから右の子、そして最終的に現ノードのキー-値の組に
探索関数をコールします。
G_LEVEL_ORDERは、
GNodeに対してのみ意味を
もちます。GTreeでは、
用をなしません。
GTreeの探索
#include <glib.h>
void g_tree_traverse
GTree*
treeGTraverseFunc
traverse_funcGTraverseType
traverse_typegpointer
data
GNode
GNodeはN-法ツリーで、
親と子リストをダブルリンクリストとして実装されています。
それゆえ、リスト操作の大部分が、
GNode API で相似体です。
また、様々な方法でツリーをたどることもできます。
以下はノードの宣言です。
typedef struct _GNode GNode;
struct _GNode
{
gpointer data;
GNode *next;
GNode *prev;
GNode *parent;
GNode *children;
};
GNodeにアクセスするマクロが存在し、
に示されています。
GListを用いた場合のように、
dataメンバは、
直接使われるでしょう。
これらのマクロは、それぞれnext、
prev、
childrenメンバを返します。
更に、引数がNULLかどうか
非参照とする前にチェックし、
その場合はNULLを返します。
GNodeメンバにアクセス
#include <glib.h>
g_node_prev_sibling
node
g_node_next_sibling
node
g_node_first_child
node
ノードを作成するのに便利な_new()関数が
提供されています()。
g_node_new() は、
dataは含めますが、
子及び親ノードを含まないノードを作成します。とりわけ、
g_node_new()は、ルートノード作成の場合のみ
用いられます。
というのは、必要に応じて自動的に新しいノードを作成する便利な
マクロが提供されているからです。
GNodeの作成
#include <glib.h>
GNode* g_node_new
gpointer
data
ツリーをビルドするためには、
に示されているような
基本的操作がとられます。各々の操作が、直接追加されたノードを
返します。
便宜上ループを記述またはツリーに再帰するとします。
GListと異なり、確実に戻り値を
無視します。
role="C">GNodeツリーのビルド
#include <glib.h>
GNode* g_node_insert
GNode*
parentgint
positionGNode*
node
GNode*
g_node_insert_before
GNode*
parentGNode*
siblingGNode*
node
GNode* g_node_prepend
GNode*
parentGNode*
node
に示されている、
便利なマクロが基本的操作に実装されています。
g_node_append()は、
g_node_prepend()と類似します。
その他は、data引数を取り、
そのため自動的にノードを割り当て、適した基本操作をコールします。
GNodeのビルド
#include <glib.h>
g_node_append
parentnode
g_node_insert_data
parentpositiondata
g_node_insert_data_before
parentsiblingdata
g_node_prepend_data
parentdata
g_node_append_data
parentdata
ツリーからノードを取り除くには、
に示されている
2つの関数があります。g_node_destroy()は、
ツリーからノードや全子ノードを破棄して、ノードを削除します。
g_node_unlink()はノードを取り除き、
そこにルートノードを作ります。つまり、独立しているツリーにサブツリー
を備えます。
GNodeの破棄
#include <glib.h>
void g_node_destroy
GNode*
root
void g_node_unlink
GNode*
node
GNodeツリーの上位下位を見分ける
2つのマクロが、に
示されています。ルートノードは、親及び同胞を全く伴わないノード
として定義されています。リーフノードは、子を持ちません。
GNodeの重要点
#include <glib.h>
G_NODE_IS_ROOT
node
G_NODE_IS_LEAF
node
GNodeについての役立つ情報を
報告するようglibに問うことができます。というのは、その
ルートノード、深さそして特別なデータポインタを含むノードの数を
備えているからです。
そのような関数が
に示されています。
GTraverseType は、
GTreeに関連して、
先に紹介されていますが、
これらはGNodeに対する可能な値です。
G_IN_ORDER は、まず
最も左のノードの子に移り、それからそのノード自身を訪れ、
そして残りのノードの子に移動します。
これはあまり役には立ちません。
大概、GTreeを伴った
用途を意図しています。
G_PRE_ORDERは
現ノードを訪れ、順次子に移っていきます。
G_POST_ORDERは、順次各子を
再帰し、現在のノードを訪れます。
G_LEVEL_ORDERは、
まずノード自体を訪れ、それから各ノードの子、そして子の子、
さらに子の子の子などと移っていきます。つまり、各深さ0のノード、
そして各深さ1のノード、更に各深さ2のノードのように訪れます。
GNode ツリー探索関数は、
GTraverseFlags引数を持ちます。
これは、探索の性質を変更するのに用いられるビットフィールドです。
現在は、3つのフラグのみ存在します—リーフノードのみ、または
非リーフノード、もしくは全ノードを訪れることができます。
G_TRAVERSE_LEAFSは、
リーフノードのみ探索します。
G_TRAVERSE_NON_LEAFSは、
非リーフノードのみ探索します。
G_TRAVERSE_ALLは、単なる
(G_TRAVERSE_LEAFS |
G_TRAVERSE_NON_LEAFS)のショートカットです。
GNode プロパティ
#include <glib.h>
guint g_node_n_nodes
GNode*
rootGTraverseFlags
flags
GNode* g_node_get_root
GNode*
node
gboolean
g_node_is_ancestor GNode*
nodeGNode*
descendant
guint g_node_depth
GNode*
node
GNode* g_node_find
GNode*
rootGTraverseType
orderGTraverseFlags
flagsgpointer
data
GNode関数を残すのは簡単で、
その大部分が単なるノードリストの子上の操作です。
は、
それを示しています。GNode
に特有な2つの関数タイプ宣言があります。
typedef gboolean (*GNodeTraverseFunc) (GNode* node, gpointer data);
typedef void (*GNodeForeachFunc) (GNode* node, gpointer data);
これらは、訪れているノードへのポインタと提供するユーザデータで
コールされます。GNodeTraverseFunc
は進行の過程で探索を停止するとTRUE
を返します。それゆえ、値によってツリーを検索するのに
g_node_traverse()と組み合わせ
GNodeTraverseFuncを用いることが
可能です。
GNodeへのアクセス
#include <glib.h>
void g_node_traverse
GNode*
rootGTraverseType
orderGTraverseFlags
flagsgint
max_depthGNodeTraverseFunc
funcgpointer
data
guint g_node_max_height
GNode*
root
void
g_node_children_foreach
GNode*
nodeGTraverseFlags
flagsGNodeForeachFunc
funcgpointer
data
void
g_node_reverse_children
GNode*
node
guint g_node_n_children
GNode*
node
GNode* g_node_nth_child
GNode*
nodeguint
n
GNode* g_node_last_child
GNode*
node
GNode* g_node_find_child
GNode*
nodeGTraverseFlags
flagsgpointer
data
gint g_node_child_position
GNode*
nodeGNode*
child
gint g_node_child_index
GNode*
nodegpointer
data
GNode*
g_node_first_sibling GNode*
node
GNode* g_node_last_sibling
GNode*
node
ハッシュ テーブル
GHashTableは、
単純なハッシュテーブルの実装ですが、連想配列に定期的巡閲を提供します。
ハッシュテーブルを用いるためには、
GHashFuncを供給しなければなりません。
その関数は、ハッシュキーを渡されたときに正整数を返します。
typedef guint (*GHashFunc) (gconstpointer key);
guint(剰余テーブルのサイズ)を返した
それぞれの値は、ハッシュの "スロット"または"バケット"に一致します。
GHashTableは、各スロットでキー-値の
リンクリストを格納することで衝突を回避します。そのため、
GHashFuncで返された
guint値は、可能な
guint値の組合せをできる限り均一に
分散させなければなりません。さもなければ、ハッシュテーブルは
リンクリストに陥ってしまいます。
GHashFuncは、
高速でなければなりません。というのは、各巡閲で用いられるからです。
GHashFuncに加え、同等にキーを
テストするにはGCompareFuncが要求
されます。多少不都合な点があり、
GHashTableは、関数記号が同じにも
関わらず、GSListや
GTreeと同方法で
GCompareFuncを取り扱いません。
ここではGCompareFuncは、等号演算子
とし、その引数が等しい場合TRUEを
返すとします。その際、qsort()
-型比較関数であってはいけません。
キー比較関数は、ハッシュの衝突が同ハッシュスロットで複数のペアに
生じた場合に、適切なキー-値のペアを見つけるために使われます。
GHashTableを作成し破棄するには、
に示されている作成関数、
または消去関数を使います。glibは、ハッシュテーブルに蓄えられたデータを
どのように破棄するのか知る方法が全くないと記憶しておいて下さい。
つまり、テーブル自体を破棄するにすぎません。
GHashTable
#include <glib.h>
GHashTable* g_hash_table_new
GHashFunc
hash_funcGCompareFunc
key_compare_func
void g_hash_table_destroy
GHashTable*
hash_table
最も一般的なキー、整数、ポインタ、そして文字列に対しては、すぐに使用
できるハッシュや比較関数が提供されています。
これらは、に示されています。
整数用の関数は、gintへのポインタも
含んでいます。gint自体より
優れているかもしれません。ハッシュ関数の引数として、
g_hash_table_new()に
NULLを渡すとデフォルトでは、
g_direct_hash()が用いられます。
キー同等機能としてNULLを渡すと単純に
ポインタ比較が使われます。
(g_direct_equal()と同等ですが、
関数のコールは行われません。 )
前書き
ハッシュ/比較
#include <glib.h>
guint g_int_hash
gconstpointer
v
gint g_int_equal
gconstpointer
v1gconstpointer
v2
guint g_direct_hash
gconstpointer
v
gint g_direct_equal
gconstpointer
v1gconstpointer
v2
guint g_str_hash
gconstpointer
v
gint g_str_equal
gconstpointer
v1gconstpointer
v2
ハッシュの処理は単純です。
ルーチンがに
まとめられています。挿入は、キーまたは値をコピーしているわけでは
ありません。これらは、まさに提供していると
いったようにテーブルに入れられ、同キーを使用している既存の
キー-値のペアは上書きされます。(ここでの"同キー"は、ハッシュや
同等の機能で定義されます。覚えておいて下さい。)
もしこれが問題だとしたら、挿入の前に調べて、
取り除かなければなりません。特に注意しなければならないのは動的にキー
及び値を割り当てている場合です。
g_hash_table_lookup()は、発見した値をキーと
連結し、値がない場合はNULL
を返します。ときには、そうならないこともあります。例えば、
NULLが、
それ自体有効値ということもあります。
キーとして文字列を使用している場合、特に動的に割り当てられた文字列の
場合、キーがテーブル内にあると知っている程度では十分ではない
かもしれません。ハッシュテーブルが、
キー"foo"を表示するのに使っている
正確なgchar*を検索する必要性が考え
られます。2つ目のlookup関数は、このような場合に提供されています。
g_hash_table_lookup_extended()は、成功すると
TRUEを返します。そして、
TRUEを返すと、与えられた場所で
発見したキーと値を格納します。
GHashTableの処理
#include <glib.h>
void g_hash_table_insert
GHashTable*
hash_tablegpointer
keygpointer
value
void g_hash_table_remove
GHashTable *
hash_tablegconstpointer
key
gpointer g_hash_table_lookup
GHashTable *
hash_tablegconstpointer
key
gboolean
g_hash_table_lookup_extended
GHashTable*
hash_tablegconstpointer
lookup_keygpointer*
orig_keygpointer*
value
GHashTable は、内部配列を確保します。
その大きさは、プライムです。更に、テーブルに格納されたキー-値の数を
記憶します。有効なスロットに対するペアの平均的数が、
0.3 (またはそれくらい)以下になると、配列はより小さくなります。
3以上になると、配列は衝突を減らすためより大きくなります。
大きさの変更は、挿入及びテーブルからペアを取り除くと
自動的に行われます。これによってハッシュテーブルのメモリ使用が
最適であるのは確実です。残念ながら、挿入または除去の数を大きくし、
何度もハッシュテーブルを再構築するのは効率がよくありません。
ハッシュテーブルが凍結可能であれば、
問題は解決します。大きさの変更が一時的に制御されるということです。
項目の追加や削除を行うと、単純にテーブルを解凍
するということです。結果としては、最適のサイズ計算になります。
(しかしながら注意が必要です。凍結したテーブルは、
大量のデータを加えると多数のハッシュ衝突に終わることがあります。
lookupを行う前解凍に従って、これを回復しなければなりません。)
関数は、に示されています。
GHashTableの凍結と解凍
#include <glib.h>
void g_hash_table_freeze
GHashTable*
hash_table
void g_hash_table_thaw
GHashTable*
hash_table
その他の特徴
本書では、glibの全特色を取り扱う程の余地がありません。
"そこに本当に必要な機能は..."と考え悩んでいる
と思ったら、glibをあたってみるといいでしょう。—
http://www.gtk.org/
のglib.hやglibドキュメントは、
優れています。
これは、まだ触れていない機能の簡単なリストです。
FLOAT_MAX等。
多くの数値型と同等です。
バイト順変換。
g_memmove() は、memmove()
より移植性があります。
G_DIR_SEPARATORは、 Windows/UNIX
の相違を処理します。
G_VA_COPYは、移植式方法で
va_listをコピーします。
移植式方法でコンパイラ拡張の使用を可能にする多数のマクロ。
(特にgcc拡張。)
g_htonl()の移植とその他の
ホスト-ネットワーク変換。
GCache 通常のキャッシュ機能。
"コールバックメンテナンス" ルーチン—コールバックの登録
及び登録解除。
g_log()機能は、可変なログレベルやプラグ可能な
出力ルーチンを使って、警告、メッセージ等を出力可能にします。
GMemChunk機能は、
g_malloc()と比較した効率面を考え、
小さな数々のメモリの大きなプールを割り当てられるようにします。
例えば、GListでの実装に
用いられます。
タイマー機能。
便利性/移植性ルーチン。ユーザのホームディレクトリを確保し、
/tmpディレクトリの名称を得たり、
それに類似することを行います。
ファイル名操作。g_basename()や
g_path_is_absolute()があります。
ビットフィールド操作。
強化文字列及び配列クラス。ポインタ及びバイト配列。
GQuark—2通りの文字列から
整数識別子へのマッピング。
データを文字列または任意のポインタで連結するルーチン。
レキシカル走査。
タブ完了。
カレンダ的/日時的-計算機能。
一般的イベントループの大要。GTK+イベントループ実装に使用されます。
移植可能なスレッドの大要。
まだ、glibに存在しない通常役立つルーチンを必要とする際は、glibの形式で
それを書き、ライブラリに貢献していただけたらと思います。設計、デバッグ、
そしてメンテナンスでフリーの恩恵を得、他のプログラマは、今度はあなたが
作成された機能から利点を得ます。これを読んでいる間にも、必要とする機能が
すでにglibの最新バージョンで存在する可能性もあるのです。
日本語翻訳:レーザーファイブ(info@laser5.co.jp)