Cpp クラス const

提供:yonewiki
2022年9月26日 (月) 11:17時点におけるYo-net (トーク | 投稿記録)による版 (ページの作成:「C++に戻る <table class="mbox-small" style="border:1px solid #aaa; background-color:#f9f9f9; width:22em;" id="RealTitleBanner"> <tr> <td style="width:1px;"></td> <td class="mbox-text plainlist" style="">本来の表記は「<b><span id="RealTitle" style="font-size:large;">C++(Cpp) クラス const</span></b>」です。この記事に付けられた題名は{{記事名の制約}}から不正確なものとなっています。</t…」)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)

C++に戻る


本来の表記は「C++(Cpp) クラス const」です。この記事に付けられた題名はテンプレート:記事名の制約から不正確なものとなっています。

※このページではC++にのみ存在する機能として、記事タイトルがC++ クラス constになっています。

クラス const

クラスとconstキーワードの関係について、この項目で記述したいと思います。変数で使っていたconstキーワードとはまた違った使い方をしますが、基本的な理念は同じです。つまりは変数の中に格納した値を初期化や初期値から変更されないように定数化・保護するということを目的としています。変えられたくない。変えられると動かなくなる。とか、変えられても大丈夫なようにチェックするということから解放されたい。こういったプログラマの都合により使われるキーワードです。


constキーワードは大きなクラスや標準的に配布されるプログラムでも積極的に使われていますので、クラスに関連するところで、このキーワードが使われた時、どういった効力があって、何がどのていど保護されるのかを理解しておかないと、値を変更する手順の考え方の齟齬がクラス開発者とそれを利用する人の中で発生したり、クラス開発者自身の中でうまく、保護がかけられていない状態になったりします。


キャストの記事でもconst外しの解説あたりで、ポインタの中身のconstとポインタ変数自身のconstについて紹介しましたが、ここでも改めてconstの基本について復習しておきます。

  • 定数変数の定義
const int nSize = 1000;
もしくは
int const nSize = 1000;
  • ポインタでの定数変数(アドレス差し替えによる中身の変更発生はOK)
const int* pnSize = &pnSize2;
もしくは
int const * pnSize = &pnSize2;

const宣言されていないポインタ変数に上記のような方法で宣言された変数のアドレスを代入することもできません。その代入して移し替えたポインタ変数から中身が変更される可能性が生じるためです。

  • アドレス変更できないポインタでの定数変数
const int * const pnSize = new int(1000);
もしくは
int const * const pnSize = new int(1000);

となります。そしてキャストでは、このconst外しなる、奥の手があることも説明しました。つまり、constは保護したつもり程度の役割であって、完璧ではない。どうしても変更させないようにするには、自分でチェックするプログラムを組むのが良いかと思いますが、あまり根を詰めないほうがいいと思います。プログラムがどう使われるものなのかによって、その厳しさを変えていくくらいの態度でよいのだと感じます。


それではクラスにおけるconstにはどのようなものがあるのか見てみましょう。


クラスにおけるconstのだいたいのことがわかるサンプルを作ってみました。


CConst001.h

#pragma once
class CConst001
{
private:
  const int m_constnValue;
  const int m_constnValue2;
  int m_nValue;
public:
  const int mpub_constnValue;

  void mfpub_Set_m_nValue(int nPara1);
  void mfpub_Set_m_nValueConstPara(int nPara1);
  int  mfpub_Get_m_nValue();
  int  mfpub_Get_m_nValue() const;
  int  mfpubconst_Get_m_nValue() const;

  CConst001(void);
  ~CConst001(void);
};

CConst001.cpp

#include "stdafx.h"
#include "Const001.h"

void CConst001::mfpub_Set_m_nValue(int nPara1){
  nPara1 = mpub_constnValue;
  m_nValue = 1000;
  printf("mfpub_Set_m_nValue\n", mpub_constnValue);
  printf("m_nValue        =%d\n", m_nValue);
  printf("mpub_constnValue=%d\n", mpub_constnValue);
}

void CConst001::mfpub_Set_m_nValueConstPara(const int nPara1){
  //nPara1 = mpub_nValue;★1.const付きの引数は関数内で変更できない。
  m_nValue = 1000;
  printf("mfpub_Set_m_nValueConstPara\n", mpub_constnValue);
  printf("m_nValue        =%d\n", m_nValue);
  printf("mpub_constnValue=%d\n", mpub_constnValue);
}

//★2.const付きのオブジェクトからは呼び出し不可能な関数
int CConst001::mfpub_Get_m_nValue(){
  m_nValue = 10;
  return m_nValue;
}

//★3.const付きのオブジェクトからも呼び出し可能な関数 constメンバ関数
int CConst001::mfpubconst_Get_m_nValue() const{
  //mpub_nValue = 10;★7.constメンバ関数ではメンバ変数の変更は出来ない。
  return m_nValue;
}


//★4.constメンバ関数はオーバーロードできます。
int CConst001::mfpub_Get_m_nValue() const{
  //mpub_nValue = 10;★7.constメンバ関数ではメンバ変数の変更は出来ない。
  return m_nValue;
}


CConst001::CConst001(void):mpub_constnValue(0),m_constnValue(0),m_constnValue2(0)//★10.constメンバ変数の初期化 const以外も初期化できる。
{
  //mpub_constnValue = 100;★11.コンストラクタでもconstメンバ変数への代入はできない。
}


CConst001::~CConst001(void)
{
}

sample_main.cpp

#include "stdafx.h"
#include "Const001.h"

int _tmain(int argc, _TCHAR* argv[])
{
  CConst001* CConst001_Instance = new CConst001;//★5.
  CConst001_Instance->mfpub_Set_m_nValue(1000);
  //CConst001_Instance->mpub_constnValue = 1000;★8.constメンバ変数の呼び出しは出来ない。

  const CConst001* CConst001_constInstance = new CConst001;//★6.
  CConst001_constInstance->mfpubconst_Get_m_nValue();
  CConst001_constInstance->mfpub_Get_m_nValue();//★9.constメンバ関数以外も呼び出せる。

  return 0;
}

と、まずは長々とサンプルを貼りましたので、この時点でもう読み進めている人はあまりいないと思いますが、読み続けてくれている希少な読者のためにも説明はこの後に記載していきたいと思います。


他のサイトの解説の方がうまく説明されていると感じる人も多いと思います。読み終えた後に、このサイトで勉強してよかったと思えるくらいのメリットを…爪痕を残せればとは思っていますが…。所詮、趣味プログラマのたわごと。ぬかしよるわ。って感じでしょうか。ちなみにファイル名にxxxx001.cppとかいう数字のサフィックス(接尾句)がついていますが、これはですね。CConst.cppだと既に有名なクラスがあったりしたら嫌だなと思ってつけているだけでして、特に深い意味はありません。001が既に存在していたら002にすればいいだけなので、楽でしょ。で、バージョンをすっかりと差し替えたい場合とかに数字をあげたり、いろいろと使い道のあるサフィックスにしているつもりです。え?それだと、イチイチクラス名が長くなり過ぎだろ?あんたバカ!?みたいな意見もあるかと思いますが、変数やクラス名が長くなるのは、自分みたいな趣味プログラマは余裕で受け入れることができる度量があるわけです。なぜなら根をつめてプログラミングすることはあまりないので、たまのプログラミングで変数が長いくらいで疲れることはないので、読み返してわかりやすいことのほうが優先されています。数式の計算が長くなるときはコメントで何をやっているかを概要をいれたりします。とにかく、何の変数がどんな型になっているのかわかりやすくします。これが自己流です。でもいつもいつも長い訳ではありません。できるだけってだけです。疲れてるときやどうでもいいとき、あるいは単純プログラムや一夜漬けや連続作業時は、aとかbとか適当な変数を使うこともあります。


と、ここにきて、上記のような、どうでもいい説明を入れてみました。


引数

4行目と12行目の関数では、メンバ関数の引数にconstがあるかないかの違いになっています。メンバ関数のプロトタイプ宣言(xxx.hファイルの方の記述)ではconstをつけてもつけなくても、効力はありません。

void CConst001::mfpub_Set_m_nValue(int nPara1){


void CConst001::mfpub_Set_m_nValueConstPara(const int nPara1){

★1.部分のようにnPara1という引数の中身を関数の中で書き換えて利用するようなことがconst付きの方では出来なくなっています。もちろん呼び出す側の引数部分においては、constであろうとconstで無かろうと問題にはなりません。但し呼び出す側で既にconstになっているポインタ型変数や参照型変数をconstになっていない引数をうけとる関数を呼び出すことは違反です。中身が書き換わってしまう可能性があるからです。このあたりのやりとりに関する規則を具体的に理解するには、そのイメージを説明する絵があった方がわかりやすいのかもしれません。


constメンバ関数

★2.★3.★4.の部分の内、★3.★4.のように関数名の最後にconstと記述するようなメンバ関数をconstメンバ関数と呼んでいます。メイン関数側の★6.部分のように呼び出し側のクラスオブジェクトそのものがすでにconstで定義されているか、どうかによって動作が変わる関数と考えていいと思います。★6.部分のようにconstで定義されている場合には★3.や★4.のようなconstメンバ関数として、メンバ関数内でメンバ変数を書き換えないことを保証した関数しか呼び出すことが出来ないオブジェクトになっています。★5.部分のようなconstで定義されていないオブジェクトであれば、★2.でも★3.でも★4.でも、つまりconstメンバ関数であっても、★9.部分のように、そうでなくても呼び出すことはできます。★2.と★4.のように同じ関数名でconstメンバ関数か、そうでない普通のメンバ関数かだけが違うような同名の関数を定義できます。同名の関数がある場合は、特にどちらを呼び出すかを指定しなくてもオブジェクトがconstであれば、constメンバ関数が選ばれて呼び出されます。通常のオブジェクトなら通常のメンバ変数が選ばれて呼び出されます。このように自動的に利用される関数が振り分けられる仕組みを利用し、同じ関数名で二つ目の関数を定義することを関数のオーバーロードと呼びます。


constにはconstを!constでなければconstでないものをという感じです。★8.部分のごとくです。


constメンバ関数がなければconstが定義されたオブジェクトからの呼び出しはエラーになります。もちろんconstメンバ関数内で★7.部分のようにメンバ変数を呼び出して、変数の中身を変更するような処理を記述することはできません。


プロトタイプ宣言(xxx.hファイル側の記述)をするときにもconstメンバ関数には、関数名の定義の後ろにconstのキーワードを記述する必要があります。

constメンバ変数

constが定義されたクラス内のメンバ変数をconstメンバ変数と呼ぶような決まりは特にないですが、constが定義されたメンバ変数はコンストラクタの記述によって初期化をしなければ、初期値を与えることができません。初期値を与えるには★10.部分の方法でしか初期値を与えることはできません。★11.部分のようにコンストラクタ内で代入するような形式でも駄目だし、ヘッダファイル側のメンバ変数宣言のところで初期値を代入することでも初期値を与えることはできません。


CConst001::CConst001(void):mpub_constnValue(0),m_constnValue(0),m_constnValue2(0)


コンストラクタの関数の後に : を記述して、その後ろに引数を定義していきます。文字あるいは文字列のような一重引用符や二重引用符のリテラルを()の中に記述することもできます。この初期化は特にconstでないメンバ変数でなくても初期化できます。面倒ですが…。


const外し(const_cast) or const戻し

const_castを使うといろいろな場面でconstで指定されている変数(ポインタ変数やオブジェクトも含める)であったものをconst無しの扱いに変更が出来ます。それじゃ、constでは保護されないじゃん。なんじゃそりゃ。って思うやもしれませんが、constというものはそういうものです。では、どれくらいのことがやれるのか試してみましょう。


constポインタ変数→通常メンバ関数のポインタ変数(参照変数)引数

まず、メンバ関数の引数はconstではないのに、呼び出す側のプログラムではconstになってしまっている場合。これはメンバ関数を呼び出すときだけに限らず、通常のクラスではない関数の引数のときでも同じことなのですが、C++ではメンバ関数を多用しますので、基本的な作法として、一つ目の例としてもってきました。

例えばsample_main.cpp側で

  
  省略
  
int _tmain(int argc, _TCHAR* argv[])
{
  
  省略
  
  const int* constpnValue = new int(2000);//★20.
  
  省略
  
}

★20.部分のように値を変えられたくないint型のポインタ変数を実体化してconstに指定している場合で、以下に示すメンバ関数のようにint*型の引数を必要としている関数があった場合。


Const001.h(メンバ関数追加)

  
  省略
  
class CConst001
{
  
  省略
  
  public:
    void mfpub_Set_m_nValueConstpPara(int* pnPara1); 
  
  省略
  
}

Const001.cpp(メンバ関数追加)

  
  省略
  
void CConst001::mfpub_Set_m_nValueConstpPara(int* pnPara1){
  *pnPara1 = mpub_constnValue;
  m_nValue = 1000;
  printf("mfpub_Set_m_nValueConstpPara\n");
  printf("m_nValue        =%d\n", m_nValue);
  printf("mpub_constnValue=%d\n", mpub_constnValue);
}
  
  省略
  
}

のように引数の中身が書き換えられてしまう関数があった場合でそのまま、呼び出し側のconst変数を使わなければならないという、ありえないような状況に対応するならば、メイン関数の呼び出しは以下のようにcastすることで呼び出せてしまいます。

例えばsample_main.cpp側で

int _tmain(int argc, _TCHAR* argv[])
{
  
  省略
  
  CConst001* CConst001_Instance = new CConst001;
  const int* constpnValue = new int(2000);
  CConst001_Instance->mfpub_Set_m_nValueConstpPara(const_cast<int *>(constpnValue));//★21.
  
  省略
  
}

★21.部分のように引数に

const_cast<int *>(constpnValue)

とします。


ワ、ワイルドだろ?っていう、そんな感じですが、constな・ん・か、そんなの関係ねぇ~。そんなの関係ねぇ~。ハイ。おっぱっぴ~。こういう具合にconstの効力なんてのは、外そうと思えば外せるわけです。この場合はC++プログラムの理念から外れていることになります。今回の場合はメンバ関数内で書き換えられるわけだからメイン関数側でconstに指定したものを引数にしたことが間違っていることになります。メンバ関数の名前がSetって名前だから、本来は書き換えられるべきでない仕様のハズ。だとしたら、メンバ関数名がおかしいとも考えられるし、間違えて無意味に引数を書き換えてしまうようなメンバ関数になってしまっている可能性もあります。そうするとメンバ関数の引数側でconst int*という型にしておかなかったのが間違いだとも考えられます。


つまり、const_castを使っているという事は何かの歯車が噛み合っていないそんな感じです。それでも、歯車が壊れない程度にガチャガチャ回し続けている社会というのは、よくあります。const_castはどんなことがあろうと、一日たりとも止めることが出来ない社会を成り立たせるために、法に触れない範囲で倫理的に許される範囲であるかどうかを判断しつつも、悪に手を染めてまで、誰かがドラマの悪役を演じて、その人が思う方向へ突きススメルためにある。そういうことです。


以降でも、いくつかconst_castのパターンを記述していこうと思いますが、誰がどう悪いのかは説明しません。やろうと思えばできる。家のような大切な鍵でもキーインロックをやらかしてしまったら鍵の救急車なる謎の部隊を呼ぶことができて、簡単に施錠を解除してくれます。それで家の中に入れたとき、あぁ世界は素晴らしいって思えると思います。あぁC++は素晴らしい。あれ?なんか違うか?


と、まぁうまいこと例えたつもりです。続きのパターンはまたぼちぼち書いていきます。


constメンバ関数内からメンバ関数呼び出し

constメンバ関数内から通常のメンバ関数を呼び出すことはできませんが、これもconst_castによって回避することができます。まずは、constメンバ関数内から通常のメンバ関数が呼び出せない動作について確かめるためのプログラムを作ってみます。


サンプルは以下のとおりです。 Const001.cpp(3つのメンバ関数の中身を変更しました。)


省略

int CConst001::mfpubconst_Get_m_nValue() const{
  int nValue = mfpub_Get_m_nValue();//★30.
  printf("■mfpubconst_Get_m_nValue() const\n");
  printf("nValue=%d\n",nValue);
  return m_nValue;
}


int CConst001::mfpub_Get_m_nValue()      {
  printf("■mfpub_Get_m_nValue()\n");
  m_nValue = 10;31.
  return m_nValue;
}

//★32.
int CConst001::mfpub_Get_m_nValue() const{
  printf("■mfpub_Get_m_nValue() const\n");
  //m_nValue = 20;■constメンバ関数ではメンバ変数の変更は出来ない。
  return m_nValue;
}

省略

★30.部分でconstメンバ関数内から通常のメンバ関数を呼び出すようなつもりのプログラムを追記しました。


それでsample_main.cppは変更せずに実行するとします。ちなみに、上記の変更について実際に呼び出しされる部分はこうでした。


省略

int _tmain(int argc, _TCHAR* argv[])
{

省略

  const CConst001* CConst001_constInstance = new CConst001;
  CConst001_constInstance->mfpubconst_Get_m_nValue();

省略

}

そうすると、実行してみると以下のように出力されます。

実行結果

■mfpubconst_Get_m_nValue() const
■mfpub_Get_m_nValue() const
nValue=-842150451

という具合に、なります。★30.部分の記述では、動かないようなことを言っていたのに、あれ、呼び出せた?と一瞬思っちゃいますが、実はここでもconstにはconstを!の原理が働いています。


int nValue = mfpub_Get_m_nValue();


もし、mfpub_Get_m_nValue()による呼び出しができているとしたら、★31.が実行されてnValueの値は10になっているはずですが、出力ではまったく違う値になっています。実は予め用意していた、もう一方の★32.のconstメンバ関数が実行されています。これはconstオブジェクトではconstメンバ関数しか呼べないということの復習になるでしょうか?


constメンバ関数が用意されていなければ、コンパイルエラーになります。


で、★31が実行されるには、★30.の部分の呼び出しがconstではないオブジェクトから呼ばれたことにすればよいことになります。そのようにするには以下のように★30.部分を書き換えます。


Const001.cpp


省略

int CConst001::mfpubconst_Get_m_nValue() const{
  int nValue = const_cast<CConst001*>(this)->mfpub_Get_m_nValue();//★40.
  printf("■mfpubconst_Get_m_nValue() const\n");
  printf("nValue=%d\n",nValue);
  return m_nValue;
}

省略

★40.部分のようにconst_castによって、クラスのthisポインタ変数の型をconst CConst001*型→CConst001*型に変換するようにすると、実行結果は以下のように変わります。


実行結果

■mfpubconst_Get_m_nValue() const
■mfpub_Get_m_nValue()
nValue=10

になります。


const_cast<CConst001*>(this)->mfpub_Get_m_nValue();

const_cast<const CConst001*>(this)->mfpub_Get_m_nValue();


に変更すると、またconstオブジェクトに戻るので、実行結果も元に戻ります。thisポインタに関する理解がなければ、この項目のconst外しサンプルはイマイチわからないものに感じるかもしれません。thisポインタに関する理解があれば、他にも呼び出す方法はあると思いつく人もいるかもしれません。


このことから、constとして生成されたオブジェクトからconstメンバ関数を呼ばれたときのメンバ関数内でのthisポインタの型はconst CConst001*のように constが付与されたポインタのように振る舞うことが分かります。


長い文章になってしまいましたが、概ねの説明は完了です。


C++に戻る