#pragma twice

KAB-studio > プログラミング > #pragma twice > 116 Version 6.16 気に掛ける範囲を狭める!

#pragma twice 116 Version 6.16 気に掛ける範囲を狭める!

前のページへ 表紙・目次へ 次のページへ

 Version 6.16
気に掛ける範囲を狭める!

「前回、バグをコンパイルエラーで見つける例を紹介して、その最後で〈気
に掛ける範囲〉についてちょっと触れました」
『でもよく分かんなかった』
「そだね、〈気に掛ける範囲〉ってちょっと分かりにくいよね。そこで、変
数のスコープを復習しながら、ゆっくり見ていきましょう」
『ゆっくり、ねぇ』
「そう、大事なとこだしね。まずは〈グローバル変数〉から。おなじみ
CDebugDlg::OnButton1() の入ってるファイルの上の方」

// DebugDlg.cpp : インプリメンテーション ファイル
//

#include "stdafx.h"
#include "Debug.h"
#include "DebugDlg.h"

int g_i;    // ここに追加。

#ifdef _DEBUG
// 以下略

『そういえばこんなとこにも変数作れるんだよね』
「こんなとこに作る変数を〈グローバル変数〉って言います。グローバルっ
て名の通り、この g_i は DebugDlg.cpp の中ならどこからでも使えます」

void CDebugDlg::OnButton1() 
{
    g_i = 500;
    TRACE( "%d\n", g_i );
    // 500
}

『ってこれは CDebugDlg::OnButton1() で使ってるけど、他でも使える?』
「もちろん」

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
    g_i = 500;
    TRACE( "%d\n", g_i );
    // 500
    //{{AFX_DATA_INIT(CAboutDlg)
    //}}AFX_DATA_INIT
}

『げ、普段気にも留めてなかった謎の関数の中で……』
「これは CAboutDlg のだから、試す時はダイアログの左上のアイコンを右
クリックしてバージョン情報を表示しないといけないから」
『ホントだ、ちゃんと出た……』
「こんなふうに、グローバル変数は、作られたファイルの中で、作られた後
のどの関数からも使えます」
『なんか便利そう……』
「が! その分注意しなきゃいけないんです」
『どゆこと? それが〈気に掛ける範囲〉ってヤツ?』
「そうなんです。もしこういうふうに作ったら、 g_i を書き換えられる範
囲がファイル全体に広がります。ってことは、いつ書き換えられるか心配し
なきゃいけない範囲が広がるってこと」
『なんかイメージ掴みにくいけど……』
「もし火美ちゃんが、今の g_i をダイアログ用に使おうと思ってたとしま
す」
『思いました!』
「そのときついうっかり CAboutDlg のメンバ関数で値を書き換えちゃった
ら……」
『そりゃまずい。でも、ついうっかり?』
「もしくはわざと。さっき火美ちゃん〈便利そう〉って言ったでしょ」
『うん言った。そっか、 CAboutDlg で書き換えられるのが便利、って思っ
て使うことあるかも、ってこと?』
「そゆこと。今は関数も変数も少ないけど、ちょっと本格的なプログラムに
なるとどばっと増えて、その増えた分だけ気に掛ける変数も、気に掛ける範
囲も広がっちゃうんです。そしてそこに、バグが生まれる!」
『おお! つまり書き間違えちゃったりしやすいってことね』
「だから、まずグローバル変数は作っちゃダメ!」
『ダメなんすか』
「そう、ダメ。絶対ダメ、って言ってもいいくらいダメ」
『なんか押し強いね、今日は……』
「まーでも難しい部分もあるんだけど……それが、グローバル変数の次に心
配する範囲が広いメンバ変数」
『メンバ変数って、クラスの中でしか使えない変数だよね』
「そうそう。計算機作ったりリストボックス使ったりしたときにやったけ
ど、もう一度ちゃんと見ておこうか。今度は DebugDlg.h の中身」

// DebugDlg.h : ヘッダー ファイル
//
// 略
class CDebugDlg : public CDialog
{
    int m_i;
// 以下略。

『って、前、たしか Ver 6.04 ( No.104 ) で m_cIFStrm 作ったとこじゃ
ん。っつーかまだ残ってるし』
「じゃあそれ削除してこれに置き換えて。まあ、メンバ変数ってことで言え
ば m_i も m_cIFStrm も変わらないけどね」
『で、この m_i はメンバ変数になるんだから、メンバ関数ならどこからで
も使えるんだよね。たとえばいつもの CDebugDlg::OnButton1() で……』

void CDebugDlg::OnButton1() 
{
    m_i = 500;
    TRACE( "%d\n", m_i );
    // 500
}

『うんうん』
「でも、さっきの CAboutDlg::CAboutDlg() の中じゃ使えないんだよ」

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
    m_i = 500;  // コンパイルエラー。
    //{{AFX_DATA_INIT(CAboutDlg)
    //}}AFX_DATA_INIT
}

『そっか、 これは CDebugDlg クラスのメンバ関数じゃないから』
「そう。でもさ、これも CAboutDlg の中で使えないだけで、 CDebugDlg の
中からはどこからでも使えるから」
『さっき言ったみたいに、 CDebugDlg が大きくなったり、 m_i みたいなメ
ンバ変数が増えたら大変になっちゃうわけね』
「そゆこと。そうなってきたら、クラスを分割して分けます」
『……なにそれ』
「まー、ようするに新しく自分でクラスを作って、その中にメンバ変数やメ
ンバ関数を移すってこと。これはまた難しい話になってくるから、先に進ん
でからじっくりとね」
『いつものパターン……』
「で、メンバ変数よりも範囲が狭いのが、普通の、つまりローカル変数」

void CDebugDlg::OnButton1() 
{
    int i = 500;
    TRACE( "%d\n", i );
    // 500
}

『いつものヤツね。この i は他のメンバ関数からも呼べないわけね』
「そう。もし使いたい場合には、引数として渡せばいいから」
『なるほど』
「さて、ここからがある意味本題。たとえば文字列ポインタ」

void TraceChars( char *p_ch )
{
    TRACE( "%s\n", p_ch );    // ABCDE
}

void CDebugDlg::OnButton1() 
{
    char ch[] = "ABCDE";
    TraceChars( ch );
}

『文字列渡して出力してるね』
「これをうっかり書き換えて……」

void TraceChars( char *p_ch )
{
    TRACE( "%s\n", p_ch );    // ABCDE
    p_ch[0] = 'Z';
}

void CDebugDlg::OnButton1() 
{
    char ch[] = "ABCDE";
    TraceChars( ch );
    TRACE( "%s\n", ch );  // ZBCDE
}

『あ、 "ABCDE" の中身が書き変わっちゃった』
「もちろん、これが TraceChars() の機能ってことならいいんだけど、そう
じゃなくてこれは単なる間違い、 TraceChars() の中では書き換えないよう
にする、って場合には?」
『 const ポインタ!』
「そうでした」

void TraceChars( const char *const p_ch )
{
    p_ch[0] = 'Z';  // コンパイルエラー
}

『そうそう、こうすれば配列の中身を書き換えられないんだよね』
「そしてこれが、範囲を狭めるってこと」
『どゆこと?』
「 const じゃなかった場合、 CDebugDlg::OnButton1() の中の ch が書き
換えられる範囲は、 CDebugDlg::OnButton1() と TraceChars() の両方だっ
たわけ」
『そっか、それを const にすれば TraceChars() じゃ書き換えられなくな
るから書き換えられちゃう範囲が小さくなる、つまり気に掛ける範囲が狭ま
る!』
「そゆこと。こうやって狭く狭くしていけば、それだけ楽になるから」
『そういえば const char *const のふたつめの const は? 確か……これ
すると、ポインタが変わらなくなるんだよね。だから』

void TraceChars( const char *const p_ch )
{
    p_ch = NULL;    // コンパイルエラー
}

『なんだよね。でもこれって意味なくない? これができても、外の ch に
は関係ないような気がするし……』
「もちろん外には関係ないよ。これは自分用」
『自分用?』
「自分で TraceChars() を作る時に p_ch のアドレスを変えないように。
ポインタじゃなくて int を引数に取る時とかも結構するよ」
『ふーん』
「で、今回のもすべて、バグをコンパイルエラーで拾う方法」
『そいや、範囲の違う変数を使うとコンパイルエラーになるもんね』
「というわけで、次はコンパイルエラーじゃない方法で拾ってみましょう」

/*
    Preview Next Story!
*/
『でも const int の引数って、使われてるの見たことないんだけど』
「使ってるの僕くらいかもしれないね」
『げ、そゆこと教える?』
「でも、いいことだとは思うでしょ?」
『まあそりゃぁ……』
「というわけで次回」
< Version 6.17 ASSERT の網 >
『につづく!』
「ま、使うか使わないかは火美ちゃんが決めて」
『じゃあ使わない』
「なにーっ!?」
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。