グローバルスコープの変数は、「他のモジュールからもアクセスできるようにしたい場合」に使います。
例えば次のプログラムを見てみましょう。1つ前のトピックとよく似たC言語のプログラムです。
#ifndef document_h
#define document_h
typedef enum {
Closed,
Open
} DocumentOpenStatus;
extern DocumentOpenStatus openStatus;
void OpenDocument(const char *pDocumentName);
void CloseDocument(void);
#endif /* document_h */
#include <assert.h>
#include "document.h"
DocumentOpenStatus openStatus = Closed;
void OpenDocument(const char *pDocumentName) {
assert(openStatus == Closed);
/* ドキュメントを開く処理(省略) */
openStatus = Open;
}
void CloseDocument(void) {
assert(openStatus == Open);
/* ドキュメントを閉じる処理(省略) */
openStatus = Closed;
}
1つ前のトピックのプログラムから変わったのは、ドキュメントが開いているかどうかを示す変数openStatus
がグローバルになったことです。
「document.c」からstatic
を削除して、ヘッダーファイル「document.h」にextern
宣言を追加してあります。それにともなって、変数の型(enum
)の宣言を「document.c」から「document.h」に移動しました。
これで何ができるようになるかは、main()
を見れば分かるでしょう。
#include <stdlib.h>
#include "document.h"
int main(void) {
OpenDocument("document-name");
if (openStatus == Open) {
CloseDocument();
}
return EXIT_SUCCESS;
}
グローバルになった変数openStatus
にmain()
からアクセスして、ドキュメントが開いているかどうかを判定できるようになったのです。
グローバルにしてしまったら、変数の値が勝手に変更されないことを誰が保証してくれるのでしょう?もちろん、「この変数は更新しない」というルールを守ってコーディングできれば問題ありませんが、それはなかなか難しいことです。
プログラムの規模が大きくなるにつれグローバル変数の数が増えれば、ややこしいルールもどんどん増えていくでしょう。そして、いつか手におえないほど複雑になってしまうかもしれません。(実際に、そのようなプログラムで苦労したことがある人もいるのでは?)
ここからは、「情報隠蔽」を保ったまま(同時に気を配らなければならない範囲を狭くしたまま)、やりたいことができる方法を考えてみましょう。グローバル変数を使わずに、ドキュメントが開いているかどうかを判定できるようにするのです。
次の改良版プログラムを見てください。
#ifndef document_h
#define document_h
typedef enum {
Closed,
Open
} DocumentOpenStatus;
DocumentOpenStatus GetOpenStatus(void);
void OpenDocument(const char *pDocumentName);
void CloseDocument(void);
#endif /* document_h */
#include <assert.h>
#include "document.h"
static DocumentOpenStatus openStatus = Closed;
DocumentOpenStatus GetOpenStatus(void) {
return openStatus;
}
void OpenDocument(const char *pDocumentName) {
assert(openStatus == Closed);
/* ドキュメントを開く処理(省略) */
openStatus = Open;
}
void CloseDocument(void) {
assert(openStatus == Open);
/* ドキュメントを閉じる処理(省略) */
openStatus = Closed;
}
変数openStatus
は、static
を付けてファイルスコープに戻しました。その代わり、変数の値を返す関数GetOpenStatus()
が追加されたのが分かるでしょうか。
main()
がどうなるのかも、見てみましょう。
#include <stdlib.h>
#include "document.h"
int main(void) {
OpenDocument("document-name");
if (GetOpenStatus() == Open) {
CloseDocument();
}
return EXIT_SUCCESS;
}
追加されたGetOpenStatus()
により、ドキュメントが開いた状態かどうかを判定できています。一方、もうopenStatus
に直接アクセスすることはできません。
今回の例のように、「ファイルスコープ変数の値を取り出す関数」を用意する方法には、もうひとつ重要な効果があります。それは、「インターフェースと実装の分離」です。
GetOpenStatus()
関数は、インターフェースとして公開されています。でも、その内部でopenStatus
にアクセスしていることまでは公開していません。つまり、実装を隠しています。
そのため、インターフェースを変えることなく、あとから実装だけを変更できるのです!
例えば、ドキュメントが開いているかどうかをファイルポインタで判定するように変えたくなるかもしれません。そのために変数を差し替えても、モジュールの外からみたGetOpenStatus()
の振る舞いは変えずに済むでしょう。
static FILE *documentFile = 0;
DocumentOpenStatus GetOpenStatus(void) {
return (documentFile)? Open : Closed;
}
もしグローバル変数を使っていたら、こうした変更は簡単ではなくなります。
関数呼び出しのオーバーヘッドは、ほとんどの場合は気にする必要がないほど小さなものです。それでも気になるようなら、inline
関数を使うようにしましょう。ただし、コンパイラがinline
関数をサポートしていないケースもあります。