C++ const【機能拡張された利用方法をシーン毎に解説】

C++
この記事は約9分で読めます。

こんにちは、ナナです。

「const」修飾子はC言語でも利用できる機能であり、定数を作るための機能です。

const int C_NUM_MAX = 100;

C言語における「const」の使い方は『C言語 constの利用価値【変数を定数化する方法と利用シーン】』を見ておくとよいでしょう。

C++では、C言語における使い方はもちろんですが、新たに使いどころが追加されています。

クラスならではの「const」の新しい使い方を4つのシーンに分けて学びましょう。

本記事で学習できること
  • 参照型の引数に付与する「const」の役割と使い方
  • メンバ関数に付与する「const」の役割と使い方
  • 「const」を付与したメンバ変数の定義方法と使い方
  • defineの代わりに利用する「const」の使い方
スポンサー

シーン①:参照型の引数に付与する「const」の役割と使い方

C++のプログラムの中で使われるシーンとして最も多いのが、参照型の引数に付与する「const」です。

今後皆さんが、なんらかの本格的なC++のプログラムを見る機会があったら「const」キーワードを検索してみてください。参照変数に付与された「const」はものすごく多くヒットするはずです。

コピーコンストラクタを代表とする参照型引数の「const」

次のプログラムが代表的な参照型引数の「const」です。

class POS
{
private:
    int x;
    int y;

public:
    POS() {
        x = 0;
        y = 0;
    }

    //  コピーコンストラクタ
    POS(const POS& pos)
    {
        x = pos.x;
        y = pos.y;
    }
};

このように関数の引数で受け渡しされるクラスオブジェクトは、「const」修飾子が付与されるケースが多いです。

参照型引数に付与される「const」の役割

この「const」には

渡されたクラスオブジェクトの状態を変更しませんので安心してください!

という意味があります。

オブジェクトの安全な受け渡し

つまり、次のように「const」で定義されたオブジェクトは、メンバ変数で管理されるオブジェクトの状態を変更することができなくなります。

POS(const POS& pos)
{
    // constオブジェクトの書き換えはビルドエラー
    pos.x = 100;
    pos.y = 200;
}

このエラーは「コンパイラ」によって検知されるため、強制力を持った変更禁止をオブジェクトに指定することができるのです。

「const」は付与してもよいなのか?付与すべきなのか?

皆さんは関数定義を行う際に、引数に参照型オブジェクトを指定するときは常に自問自答しなければなりません。

この参照型オブジェクトに「const」は付与すべきか?

をです。

その関数の中で、引数で渡されたオブジェクトを変更することがないのであれば、その時は「const」を必ず付与しましょう!

設計的に変更してはならないオブジェクトに対して、強制的に変更できないようにするこの仕組みはプログラムの安全性を高めてくれる効果があるのです。

ナナ
ナナ

「const」が付与できるシーンにおいては、「const」は付けてもよいではなく付けるべきなのです。

スポンサー

シーン②:メンバ関数に付与する「const」の役割と使い方

この「const」はC++において追加された全く新しい使い方であり、C言語には存在しません。シーン①とも関連性が高いため必ず知っておきましょう。

メンバ関数に付与する「const」の定義方法

使い方はプログラムを見た方が早いでしょう。次のように、メンバ関数定義の終端に「const」を付与することができます。

#ifndef POS_H
#define POS_H

class POS
{
private:
    int x;
    int y;

public:
    POS(): x(0), y(0){}

    //「const」付きのメンバ関数
    int getX() const;
    int getY() const;

    void setX(int x);
    void setY(int y);
};

#endif  //  POS_H
#include "POS.h"

int POS::getX() const
{
	return x;
}

int POS::getY() const
{
	return y;
}

void POS::setX(int x)
{
	this->x = x;
}

void POS::setY(int y)
{
	this->y = y;
}

このように付与された「const」のメンバ関数では

自オブジェクトのメンバ変数に対する変更を禁止する効果

があります。

つまり、次のように自オブジェクトのメンバ変数「x」を変更しているsetXメンバ関数では、「const」を付与することはできません。

void POS::setX(int x) const
{
    this->x = x; // ビルドエラー発生
}

このように「const」を付与することで、間違って自オブジェクトを変更してしまう危険性を排除することができます。

ナナ
ナナ

設計的に自オブジェクトを変更することがないメンバ関数は「const」を付けておくべきです。

終端に「const」を付与することで、自オブジェクトを示すthisポインタを使ったメンバ変数の変更がないことを保証できます。

「const」が付与されたメンバ関数のもうひとつの役割

自オブジェクトを変更できない以外にもう一つの役割があります。

それが、

const」なオブジェクトに対する呼び出しを限定する役割

です。

constなオブジェクトからメンバ関数を呼び出す際には、「const」付きのメンバ関数しか呼び出すことができません。

#include <stdio.h>
#include "POS.h"

int main()
{
    const POS pos1;     //  constなオブジェクトを生成

    //「const」付きのメンバ関数
    int x = pos1.getX();    //  呼び出しOK
    int y = pos1.getY();    //  呼び出しOK

    //「const」が付いていないメンバ関数
    pos1.setX(100);         //  ビルドエラー
    pos1.setY(200);         //  ビルドエラー

    return 0;
}

この特性から、メンバ変数を変更する必要がないメンバ関数には必ず「const」を付けましょう。

そうしないとconstなオブジェクトから、本来呼び出せるはずのメンバ関数が呼び出せなくなってしまうのです。

ナナ
ナナ

「const」なオブジェクトはシーン①において高い頻度で利用されるため、正しく付与しないと構成に矛盾が生じますよ。

スポンサー

シーン③:「const」を付与したメンバ変数の定義方法と使い方

C言語の時代から「const」は変数に付与することが一般的です。

C++ではクラスという概念が登場したことにより、クラス内に定義する「メンバ変数」に対して「const」を付与する新しい使い方が生まれました。

「const」付きのメンバ変数の定義と初期化方法

constを使ったメンバ変数は次のように定義と初期化を行います。

#ifndef POS_H
#define POS_H

class POS
{
private:
    int x;
    int y;
    const int z;    //  constメンバ変数

public:
    POS(int tmpz);
};

#endif  //  POS_H
#include "POS.h"

POS::POS(int tmpz) : z(tmpz)
{
	x = 0;
	y = 0;
}

「const」付きのメンバ変数は、コンストラクタの初期化リストによって初期化を行います。

初期化すべき値は次のように、オブジェクトを生成する側がコンストラクタの引数として指定するのが一般的です。

#include <stdio.h>
#include "POS.h"

int main()
{
    POS pos1(100);  //  z要素を100で初期化
    POS pos2(200);  //  z要素を200で初期化

    return 0;
}

このようにすることで、オブジェクト毎に異なる定数値を管理することができるようになります。

初期化リストによるconstメンバ変数の初期化

「const」なメンバ変数は、オブジェクト生成後に不変のデータとして管理することができます。

コンストラクタで初期化するときは「代入」はできないことに注意しましょう。これは「const」付きの変数が「初期化」はできても「代入」ができないことが理由です。

POS::POS(int tmpz)
{
    x = 0;
    y = 0;
    z = tmpz;   //  constのメンバ変数に代入はできない
}
ナナ
ナナ

コンストラクタの初期化リストは、const以外のメンバ変数の設定も可能ですよ。

スポンサー

シーン④:#defineの代わりに利用する「const」の使い方

C++という言語は、「#define」を使ったマクロから可能な限り脱却をしようという方向性を目指しています。

「#define」はプリプロセッサによる定数を作るための機構ですね。

C++ではこのような#defineを使った定数を「const」を使った変数へと置き換えられるようになっています。

#define D_INIT_NUM  (50)    //  初期値の定数
#define D_MAXNUM    (10)    //  配列要素数の数

int main()
{
    int i;
    int num[D_MAXNUM];  

    for (i = 0; i < D_MAXNUM; i++)
    {
        num[i] = D_INIT_NUM;
    }

    return 0;
}
const int C_INIT_NUM = 50;  //  初期値の定数
const int C_MAXNUM   = 10;  //  配列要素数の数

int main()
{
    int i;
    int num[C_MAXNUM]; 

    for (i = 0; i < C_MAXNUM; i++)
    {
        num[i] = C_INIT_NUM;
    }

    return 0;
}

C++では、配列要素数も「const」の変数を使うことができるようになりました。これはC言語ではできなかったことです。

ナナ
ナナ

「#define」はプリプロセッサによる文字列置換機能であり、コンパイラによる翻訳の管轄外となっています。

C++では、可能な限りコンパイラによる管轄下にすることで、型チェックや異常な文法を検知できるように「#define」よりも「const」の利用を推奨しています。

スポンサー
C++機能解説
スポンサー
モノづくりC言語塾