C++マニアック

C++,const の使い方,usage of const,定数のメンバ変数

キーワード const は、C++ の変数(オブジェクト)が定数であることを示すものです。「オブジェクトの内容を変更できない」というアクセス制御を行うときに使うわけです。簡単ですが、ポインタと絡むと const を書く位置によって意味が変わるので、このあたり、まとめておきましょう。後半では、クラスと共に使用する場合についても解説します。

  1. 最も簡単な場合
  2. ポインタが絡む場合
  3. クラスの const なメンバ変数(定数のメンバ変数)
  4. const なクラスオブジェクトを使う場合
  5. const 関連本

最も簡単な場合 ページの先頭へ

const を使う、最も簡単な場合とは、例えば、変更しない整数の定数を定義するような場合でしょう。次の例を見てください。変数 iL1、iL2 は、定数となり、値を変更できません。

int main() {
    // int 型変数と const
    int        iL0 = 200;
    const int iL1 = 200;    // @ 
    int const iL2 = 200;    // A、    @とAは、同じ意味になる。
    {
        // 値を変更しようと試みる。
        iL0 = 300;  // OK
        iL1 = 300;  // NG、左辺は const オブジェクト
        iL2 = 300;  // NG、左辺は const オブジェクト
    }
    return 0;
}

書き方としては、上記のように const と int を逆にしても同様の意味となります。

ここでいうべきことがあるとすれば、const で定義された変数は、定数なので、値を変更できませんが、最初の定義のときにだけは、値を設定することができる、ということでしょうか。まぁ、当たり前で、そうでないと、使えないわけですが、クラスの const なメンバ変数の場合には、どうやって初期化するかということは、自明というわけではありませんね。これについては、後述します。

もう一つ言うべきは、上記で定義した iL1、iL2 は、const でない iL0 と同様に、関数 main() の中だけで有効であるということです。

C 言語には const がないので、定数を使う場合 #define を使って、次のように記述するという方法ををよく使います。この場合、その有効範囲は、ファイル全体となるので、main() の外でも使える(使えてしまう)わけです。const による定数定義とは、スコープが異なるということですね。

#define ISIZE (3)
int main() {
    ISIZE;  // main() の中でアクセス可能
}
int iSize = ISIZE;      // main() の外でもアクセス可能

上記のように定数を #define で記述するという方法は、C++ でも有効です。特に複数のファイルにまたがるグローバルなスコープの定数を記述するには適していると思います。

ナオ : 定数は全て const を使って定義しなくっちゃならない、というわけでもないわけネ。
店主 : ンだズ。
ナオ : アンタは、どこのお人じゃ。ところで C 言語には const がないってホント? あたしの持ってる C 言語の本には const のことが書いてあるけど。
店主 : おう、そうか。若い人は知らないんだね。デニスリッチーが C 言語を作った当初は、C 言語には const がなかったのさ。ANSI の規格では、付け加わっているけどね。
ナオ : そう、分かったよ、お年寄り。
店主 : 年寄りじゃないっての。

ポインタが絡む場合 ページの先頭へ

次に、const キーワードにポインタが絡む場合について解説します。次の例を見てください。赤い const はポインタの指す変数が定数であることを指定しており、緑の const はポインタそのものが定数であることを指定しています。* の前後どちらに const を書くかで、意味が変わるわけですね。

int main() {
    // int* 型変数と const
    int iA = 123;
    // ポインタを初期化する。
    const int*       piK0 = &iA;
          int* const piK1 = &iA;
    const int* const piK2 = &iA;
    {
        // ポインタが指す変数を書き換えようと試みる。
        *piK0 = 456;    // NG、左辺は const オブジェクト
        *piK1 = 456;    // OK
        *piK2 = 456;    // NG、左辺は const オブジェクト
    }
    int iB = 789;
    {
        // ポインタそのものの値を書き換えようと試みる。
        piK0 = &iB;     // OK
        piK1 = &iB;     // NG、左辺は const オブジェクト
        piK2 = &iB;     // NG、左辺は const オブジェクト
    }
    return 0;
}

クラスの const なメンバ変数(定数のメンバ変数) ページの先頭へ

クラスの const なメンバ変数を使うこともできます。次の例を見てください。

class CManiac {
private:
    const int m_iSize;         // 変数を定義し、
public:
    CManiac() : m_iSize(3) {}  // 初期化子並びで初期化する。
};

通常の const な変数と異なるのは、初期化をコンストラクタの初期化子並びで行うということです。まっ、ただそれだけです。

const なクラスオブジェクトを使う場合 ページの先頭へ

int などの C++ に組み込まれている変数型は const になり得るわけですが、自前で定義するクラスのオブジェクトを const にしようとする場合には、クラスの定義をそれなりに注意して作成する必要があります。

まず、クラスオブジェクトを使う側のコードを見てください。

int main() {
    const CManiac maniac;   // const な CManiac オブジェクトを定義する。
    maniac.GetiSize();     // メンバ関数を使う。
    return 0;
}

CManiac というクラスには、何らかのサイズを返す GetiSize() という関数が定義されているようです。上記のコードが通るようなクラス CManiac の定義の仕方ということですが、const がないものとすれば、次のような定義が考えられます。

class CManiac {
private:
    int m_iSize;    // メンバ変数
public:
    CManiac() : m_iSize(0) {}
    int GetiSize() { return m_iSize; } // メンバ変数を読む関数
};

メンバ関数 GetiSize() では、メンバ変数を読んでいるだけで、変更はしていないので const なオブジェクトで使われても問題ないように思われるのですが、コンパイラは GetiSize() がメンバ変数を変更しているかもしれないと判断し、コンパイルエラーとなってしまいます。そこで、これを避けるには、次のように、メンバ関数の定義に const キーワードをつけて、コンパイラに「この関数はメンバ変数を変更しない」ということを知らせるようにします。

class CManiac {
private:
    int m_iSize;    // メンバ変数
public:
    CManiac() : m_iSize(0) {}
    int GetiSize() const { return m_iSize; } // const をつけたメンバ変数を読む関数
};

これで、めでたく、コンパイルが通り、期待通りの動作をするようになります。このように、クラスオブジェクトを const とするには、const なクラスオブジェクトで使用するメンバ関数の定義に const を付けるようにします。

クラスオブジェクトが const であるときとそうでないときに、同じ名前のメンバ関数が必要な場合、当然、それらの動作は異なることになりますが、メンバ関数をオーバーロードすることができます。次の例を見てください。

class CManiac {
private:
    int m_iSize;    // メンバ変数
public:
    CManiac() : m_iSize(0) {}
    int GetiSize()       { return m_iSize; } // クラスオブジェクトが const で無い場合に使われるメンバ関数
    int GetiSize() const { return m_iSize; } // クラスオブジェクトが const の場合に使われるメンバ関数
};

上記では、const の場合とそうでない場合で、GetiSize() 関数の動作は同じとしましたが、異なるように定義することも可能です。どちらの関数が使われるかは、オブジェクトが const であるかどうかによって自動的に判断されます。当然ですが、const をつけた方のメンバ関数の中では、メンバ変数を変更することはできません。逆に、const をつけない方のメンバ関数では、メンバ変数を変更することができます。プログラマの自由というわけですね。

ナオ : クラスオブジェクトが const な場合と、そうでない場合で、全く動作が違うようなメンバ関数を定義することもできそうネ。
店主 : もちろん、可能じゃよ。グワハッハー。
ナオ : キャラ変わってますけど。
店主 : ダハハ、ダハハ^2、ダハハ^3、...、while(i) { ダハハ^i++; }
ナオ : すみません。どっか、行っちゃいました。ちょっとこう言うとこ、あるのよネ。