ショートカット
ファシリテーター × あり方
コーディングの向こう側
Hello, ANOTHER world!
オブジェクト指向のはなし
プログラミングのはなし
C言語実力診断クイズ
eSkillBooks
プログラミングのはなし

グローバルスコープは要らない?

はじめに断わっておきますが、このトピックで扱うのはグローバルスコープの「変数」です。グローバルスコープの「関数」は要らないはずがないですからね。

グローバルスコープの変数を使うのは「他のモジュールからもアクセスできるようにしたい場合」です。例えば次のプログラムを見て下さい(ひとつ前のトピックとほとんど同じプログラムです)。

SAMPLE
CODE
ファイル名:main.cpp
#include "sub.h"

void main()
{
    if ( gs_bOpen == false ) {
        Open( "filename" );
        Close();
    }
}
ファイル名:sub.h
#ifndef INCLUDED_SUB_H
#define INCLUDED_SUB_H

extern bool gs_bOpen;
void Open( const char *pcFileName );
void Close();

#endif // #ifndef INCLUDED_SUB_H
ファイル名:sub.cpp
#include <assert.h>
#define ASSERT(C) assert(C)

#include "sub.h"

bool gs_bOpen = false;

void Open( const char *pcFileName )
{
    ASSERT( gs_bOpen == false );
    ...
    gs_bOpen = true;
}

void Close()
{
    ASSERT( gs_bOpen == true );
    gs_bOpen = false;
    ...
}
ひとつ前のトピックのプログラムと違うところは、オープン状態を示す変数 fs_bOpen がグローバルになったことです(名前も gs_bOpen に変更されました)。そして main モジュールではこの変数にアクセスすることによってオープン状態を調査しています。

でも、ちょっと待って! main モジュールが gs_bOpen の値を変更しないという保証がどこにあるのでしょう!

もちろん gs_bOpen の値は sub 以外のモジュールが変更してはいけないというルールを守ってコーディングすれば問題ないのですが、それができないのが人間というものです。プログラムの規模が大きくなるにつれグローバル変数の数が増えれば、ややこしいルールもどんどん増えて、いつか手におえないほど複雑になってしまいます(こういうことがあなたのまわりで起こっていませんか?わたしのまわりでは日常茶飯事です!)。

いままでファイルスコープだった変数をグローバルにするということは、「情報隠蔽」が破られるということです。「情報隠蔽」を保ったまま、つまり同時に気を配らなければならない範囲を狭くしたままで、モジュール sub のオープン状態を他のモジュールから調べる仕組みを作らなければなりません。次の改良版プログラムを見て下さい。

SAMPLE
CODE
ファイル名:main.cpp
#include "sub.h"

void main()
{
    if ( IsOpen() == false ) {
        Open( "filename" );
        Close();
    }
}
ファイル名:sub.h
#ifndef INCLUDED_SUB_H
#define INCLUDED_SUB_H

bool IsOpen();
void Open( const char *pcFileName );
void Close();

#endif // #ifndef INCLUDED_SUB_H
ファイル名:sub.cpp
#include <assert.h>
#define ASSERT(C) assert(C)

#include "sub.h"

static bool fs_bOpen = false;

bool IsOpen()
{
   return fs_bOpen;
}

void Open( const char *pcFileName )
{
    ASSERT( IsOpen() == false );
    ...
    fs_bOpen = true;
}

void Close()
{
    ASSERT( IsOpen() == true );
    fs_bOpen = false;
    ...
}
オープン状態を保持する変数 fs_bOpen はファイルスコープのままです。そのかわりに、関数 IsOpen() を定義し、他のモジュールからオープン状態を検査することができるようにしています。これだと他のモジュールから fs_bOpen の値を変えることはできません。「情報隠蔽」は守られました!

このようなアプローチをとれば、グローバル変数はほとんど必要なくなります。ただし、上の例では IsOpen() 関数をコールするときに関数呼び出しのオーバーヘッドがかかります(グローバル変数として直接あつかう場合にはオーバーヘッドはかかりません)。このオーバーヘッドはほとんど気にする必要がないほど小さなものですが、どうしてもなくす必要がある場合は涙をのんでグローバルにします。なお、C++ でクラス化できる場合は inline 機能で回避できます(inline についての説明は市販の解説書に詳しく書いてあるので、ここでは省きます)。

IsOpen() 関数のように、ファイルスコープ変数にアクセスする関数を用意してグローバルなアクセスを行うという方法は、もうひとつの重要な効果をもたらします。それは、「インターフェイスと実装の分離」です。

IsOpen() 関数はオープン状態を知るためのインターフェイスを公開しています。しかし内部で fs_bOpen という変数が実際の状態を保持しているということまでは公開していません。つまり実装を隠しています。そのため、あとで実装だけを変えることができます(もちろんインターフェイスを変えずに!)。

例えば、オープン状態をファイルポインタ

static FILE *fs_fp;

がヌルかどうかによって判定するように変えたくなるかもしれません。そんなときにも IsOpen() 関数のプロトタイプを変更する必要がないため、変更による影響が他のモジュールにまで波及しなくなります。もしグローバル変数を使っていたら、大変なことになってしまいます。

(「プログラミングのはなし」は1998年1月から1999年1月にかけて作成されたコンテンツです。)