「Cpp クラス アクセス指定子」の版間の差分
(ページの作成:「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) クラス アクセス指定子</span></b>」です。この記事に付けられた題名は{{記事名の制約}}から不正確なものとなっ…」) |
編集の要約なし |
||
40行目: | 40行目: | ||
Access_Specifiers001.h | Access_Specifiers001.h | ||
<syntaxhighlight lang="cpp" line start= | <syntaxhighlight lang="cpp" line start=1-1+1> | ||
class CAccess_Specifiers001{ | class CAccess_Specifiers001{ | ||
int m_nValue; //★1.メンバ変数 省略されている場合はprivate: | int m_nValue; //★1.メンバ変数 省略されている場合はprivate: | ||
69行目: | 69行目: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Access_Specifiers001.cpp | Access_Specifiers001.cpp | ||
<syntaxhighlight lang="cpp" line start= | <syntaxhighlight lang="cpp" line start=1-1+1> | ||
#include "stdafx.h" | #include "stdafx.h" | ||
#include "Access_Specifiers001.h" | #include "Access_Specifiers001.h" | ||
136行目: | 136行目: | ||
メインプログラムや他のクラス内で以下のようにクラスを実際に利用するプログラムを記述すると以下のようになります。 | メインプログラムや他のクラス内で以下のようにクラスを実際に利用するプログラムを記述すると以下のようになります。 | ||
と上記のように利用します。_tmain関数からの呼び出し処理がいわゆる外部からの呼び出しにあたる処理になります。クラスを説明した記事でも記sample_main.cpp | と上記のように利用します。_tmain関数からの呼び出し処理がいわゆる外部からの呼び出しにあたる処理になります。クラスを説明した記事でも記sample_main.cpp | ||
<syntaxhighlight lang="cpp" line start= | <syntaxhighlight lang="cpp" line start=1-1+1> | ||
#include "stdafx.h" | #include "stdafx.h" | ||
#include "Access_Specifiers001.h" | #include "Access_Specifiers001.h" | ||
165行目: | 165行目: | ||
Access_Specifiers001.cpp | Access_Specifiers001.cpp | ||
<syntaxhighlight lang="cpp" line start= | <syntaxhighlight lang="cpp" line start=1-1+1> | ||
… | … | ||
省略 | 省略 | ||
209行目: | 209行目: | ||
IntUnit.h | IntUnit.h | ||
<syntaxhighlight lang="cpp" line start= | <syntaxhighlight lang="cpp" line start=1-1+1> | ||
#pragma once | #pragma once | ||
#include "access_specifiers001.h" | #include "access_specifiers001.h" | ||
226行目: | 226行目: | ||
IntUnit.h | IntUnit.h | ||
<syntaxhighlight lang="cpp" line start= | <syntaxhighlight lang="cpp" line start=1-1+1> | ||
#pragma once | #pragma once | ||
#include "access_specifiers001.h" | #include "access_specifiers001.h" | ||
242行目: | 242行目: | ||
IntUnit.cpp | IntUnit.cpp | ||
<syntaxhighlight lang="cpp" line start= | <syntaxhighlight lang="cpp" line start=1-1+1> | ||
#include "stdafx.h" | #include "stdafx.h" | ||
#include "IntUnit.h" | #include "IntUnit.h" | ||
265行目: | 265行目: | ||
具体的には以下のようなプログラムになります。 | 具体的には以下のようなプログラムになります。 | ||
sample_main.cpp | sample_main.cpp | ||
<syntaxhighlight lang="cpp" line start= | <syntaxhighlight lang="cpp" line start=1-1+1> | ||
#include "stdafx.h" | #include "stdafx.h" | ||
#include "Access_Specifiers001.h" | #include "Access_Specifiers001.h" |
2022年10月25日 (火) 12:37時点における最新版
C++に戻る
本来の表記は「C++(Cpp) クラス アクセス指定子」です。この記事に付けられた題名はテンプレート:記事名の制約から不正確なものとなっています。 |
※このページではC++にのみ存在する機能として、記事タイトルがC++ クラス アクセス指定子になっています。
クラス アクセス指定子
クラス宣言をするヘッダファイル側で指定するアクセス指定子は、クラスの利用時の基本的な制約を指定するものです。クラスとはじめて向き合った人は、なんのこっちゃわからんという感じになりやすい部分だと思います。クラスを利用するときの基本的な使い方を理解していないと、どうしても、このアクセス指定子の意味がわかり辛く感じるのだと思います。クラスの基本的な利用方法については既にクラスの項目に記述しましたので、軽く理解しておいたほうが良いと思います。クラスで行われる隠蔽の技術の解説についてもこの項目で長々と記述する予定です。まずは簡単な説明から記述します。
アクセス指定子には3種類のアクセスレベルが用意されています。
- public
- クラスの外側からも呼び出しできる関数や変数になる。
- protected
- クラス内部や派生されたクラスからも呼び出しできる関数や変数になる。publicとprivateの中間的なアクセスレベル。
- private
- 省略時の規定値。クラスの外側からは呼び出しできない関数や変数になる。つまり、クラス内部のみ呼び出しできるということ。
と簡単に説明しましたが、初めてクラスというものを使おうとする人にとっては、上記の簡単なまとめだけでは、まだしっくり来ないかもしれません。具体的に使ってみて理解しましょう。そして、クラスの内部とか外部とか、派生とかという表現の意味についても説明します。
C言語では、こんなのなかったんだから、覚えなくっても大丈夫ってわけには行かないのがアクセス指定子です。これを理解しないと、クラスを使うことはできないし、あやつることは出来ません。誰かが定義したクラスとかだと、うまくアクセス指定がされていて、クラス内のこの変数を変更すれば、プログラムの動作が変更されるのは、なんとなくわかるんだけど…変更しようとするとなんかコンパイルエラーが出まくって、使いこなせないイライラ!ってことになってしまいます。
この先の記述はだいたい、うっすらわかっている人にはまどろっこしい説明が続くかもしれませんが、半分くらい理解してる人でも、継承して派生させる場合にもアクセス指定子が使えるので、そのあたりの説明はもう少し先の方に記述しますので、派生時の一括定義に関するアクセス指定の件について理解していない人は、軽く下の方まで目をとおしておいた方が良いと思います。わかったつもりになるのが、プログラムを理解する上で一番危険なことです。あらゆる学問もそうだと思いますが…
それでは、派生とか継承とか内部とか外部とか、意味の分からないキーワードを理解していくためにも具体的に使ってみます。以下は説明用の単純なサンプルクラスです。
Access_Specifiers001.h
class CAccess_Specifiers001{
int m_nValue; //★1.メンバ変数 省略されている場合はprivate:
public: //★2.この宣言からpublic:
int mpub_nValue;
void Set_m_nValue(int nSetValueP);//メンバ関数
int Get_m_nValue();
protected: //★3.この宣言からprotected:
wchar_t m_wcUnit;
void mf_m_nValue1000x();
void mf_m_nValuePermil();
private: //★4.明示的なprivate:
int m_nManipulatorCnt;
public:
CAccess_Specifiers001(void);
~CAccess_Specifiers001(void);
};
Access_Specifiers001.cpp
#include "stdafx.h"
#include "Access_Specifiers001.h"
void CAccess_Specifiers001::Set_m_nValue(int nSetValueP){
m_nValue = nSetValueP;
}
int CAccess_Specifiers001::Get_m_nValue(){
return m_nValue;
}
void CAccess_Specifiers001::mf_m_nValue1000x(){
m_nValue = m_nValue * 1000;
m_nManipulatorCnt++;
}
void CAccess_Specifiers001::mf_m_nValuePermil(){
m_nValue = m_nValue / 1000;
m_nManipulatorCnt--;
}
CAccess_Specifiers001::CAccess_Specifiers001(void)
{
}
CAccess_Specifiers001::~CAccess_Specifiers001(void)
{
}
という具合のサンプルクラスを作ってみました。サンプルの割に長くなってしまいましたが、なんとなく1000倍したり1000分の1にしたりするだけの操作をするクラスです。今のところ欠陥だらけのクラスになっています。実現しようとしているクラスの機能として欠陥があるだけで、コンパイル・リンク処理は出来ます。
まずはアクセス指定子の具体的な利用部分について説明しておきます。
★1.部分のようにアクセス指定子を記述しないでクラスの中の定義を書き始めると、その部分はprivateを指定したのと同じ意味で変数や関数が定義されます。クラス内部からしか呼び出し出来ないという表現をしましたが、内部というキーワードについてはもう少し後での説明になります。変数名のプレフィックス(接頭語)としてm_をつけるのはクラスの使い方における基本的な変数命名法として定着しているものです。あとは自分独自の命名法になります。このWikiの記事を最初から全部読んでいる人には、管理人の独自の命名法について説明したと思いますので、それについては割愛します。変数名の付け方は自由です。ちょっとわかりにくい日本語を使うなら任意(にんい)ともいいますね。
次に
★2.部分で具体的にアクセス指定子を明示した部分が登場します。
アクセス指定子:
のように:を使って終端を修飾します。この指定の後ろからアクセス指定の意味が変更されるということになります。ここではpublicを指定したので、この先で指定した関数や変数からは、クラスの外部からも呼び出し出来ることになります。外部というキーワードについても後程の説明になります。
そして
★3.部分には新たにprotectedのアクセス指定子を使いました。内部と継承で派生したクラスから呼び出せる変数や関数の定義になります。
最後に
★4.部分でprivateのアクセス指定子を使っています。またクラス内部だけの呼び出しが許される変数や関数の定義に切り替わります。このように何度でもアクセス指定子は使えます。繰り返し使えますが、通常はまとめて記述する人が多いです。何の設計図もなく、1000倍や1000分の1倍するような機能を持つクラスを作り、適当にアクセス指定子を割り振っていますので、この先、都合が悪くなるとアクセス指定子の設定を変更するかもしれません。今のところ、1000分の1にしたとき小数になるから、int型だけだと都合が悪くなりそうなことだけは気づいてますが、そのままにしています。あとでプログラムを変更することになるでしょう。
たったこれだけの説明で、これだけ長くなってしまいました。記事全体が相当長くなりそうな気配がぷんぷんとしてきました。基本中の基本のことがらをこれだけ冗長的に説明するのは体が持たないような気がしますし、読んでる人も疲れるから、あまり良い記事にはならない気がしますね。
ついてこれるヤツだけ、ついて来い!(なんつって、振り返ったら誰もついてきてない感じの風景が明確に想像できます。)
メインプログラムや他のクラス内で以下のようにクラスを実際に利用するプログラムを記述すると以下のようになります。
と上記のように利用します。_tmain関数からの呼び出し処理がいわゆる外部からの呼び出しにあたる処理になります。クラスを説明した記事でも記sample_main.cpp
#include "stdafx.h"
#include "Access_Specifiers001.h"
int _tmain(int argc, _TCHAR* argv[])
{
printf("★アクセス指定子\n");
int nValue;
CAccess_Specifiers001* CAccess_Specifiers001_Instance = new CAccess_Specifiers001;
CAccess_Specifiers001_Instance->Set_m_nValue(120);
nValue = CAccess_Specifiers001_Instance->Get_m_nValue();
printf("%d\n",nValue);
return 0;
}
述しましたが、Access_Specifiers001.hファイルの★1.部分にあるようなm_nValueというint型の変数にはprivateというアクセス指定がなされていて、内部からの呼び出しだけが許されています。したがって、_tmain関数から呼び出すことはできないため、CAccess_Specifiers001_Instance->m_nValue = 120;のように変数の呼び出しをしようとするとコンパイルエラーになります。値を設定するにはSet_m_nValue関数を値を取得するにはGet_m_nValue関数を使うしかないのがCAccess_Specifiers001の取り決めになります。
これは何のために、こんなまどろっこしいことになっているのでしょうか?今のところのプログラムでは直接、m_nValueの値を設定できた方が良さそうに感じます。実はこのクラスはm_nValueには-999~999の値を最初に設定してほしいという規制を作りたいとします。そうするとSet関数内でその新たに規制のための処理を追加すれば良くて、直接に変数を呼び出して値を設定をすることはできないということが、クラスという機能を使う考え方になっています。そのためにアクセス指定子によって、直接に変数を呼び出すことを制御できることになります。このような仕組みを隠蔽と呼んでいます。
クラスのメンバ関数もメンバ変数と同じで、呼び出されるとまずい関数を作りたくなったりする場合が生じてきます。では、実際にサンプルプログラムのAccess_Specifiers001.cppの内容を書き換えて、設定値に関する規制を作ろうと思います。以下のようになります。
Access_Specifiers001.cpp
…
省略
…
void CAccess_Specifiers001::Set_m_nValue(int nSetValueP){
if(m_nValue <= -1000 || m_nValue >=1000){
printf("Warning:Set_m_nValue関数で認められない値が設定されました。(Limit -999~999)\n");
}
else{
m_nValue = nSetValueP;
}
}
…
省略
…
ちょっと冷たいエラーメッセージですが、とりあえず規制されました。Windowsとかのダイアログでの入力だと入れようとするとOKボタンが押せないとか、そういう感じの冷たさのエラーがでるのと同じです。親切ならOKボタンを押したときに、エラー値が入れられた値の欄を明示して、どの値がダメとか丁寧なエラーを出したり、入力したとたんエラーをしめすような、入力欄の名前の部分や、値そのものが赤色の文字になって不正であることを示したりします。ここでは、当初の目標であるアクセス指定子を出来るだけ丁寧に説明するというだけのサンプルであるという雑さをもちあわせたまま、無理やりにサンプルを作ります。
ク、クラス恐るべしだな。ひとつの機能の塊をもつのがクラスだから、こんな指定があるので、覚えるしかないですが、最初にこの技術をしったとき、こういった説明を読んでも尚、まだ納得してませんでした。ややこしい仕組みだなぁ。こんなのつかわなくてもプログラムなんてやれると反抗的な態度だった若かりし頃の自分を思い出します。最近の若い人はどんな風に感じるんだろうか。ICT社会だから、すぐ納得できるんだろうか。心配です。
とにもかくにもクラスの中で定義されてるメンバ関数から呼び出すのが内部(Access_Specifiers001.cpp内で記述している関数)で、それ以外は外部(上の例なら_tmain関数)ってことです。それが内部と外部です。わかっていただけていたら幸せなことです。
じゃ、あとはprotectedで説明した派生したクラスと内部だけっていうパターンを説明する感じですね。これは別の項目でも継承って奴について記述するつもりなので詳細は継承の記事を読んでもらえるといいんですけど、まずは簡単に派生ってやつのパターンを示したいと思います。でも、今日はここまでかな。
では、難しいことは考えずに継承ってやつを使ったクラスを作ってみます。継承に関する詳細は継承の記事で触れますので、ここでは継承というものが存在しいるとだけ、なんとなく理解しておくとよいと思います。VisualStudioでは具体的に以下のように操作すると、ここで作ったCAccess_Specifiers001ってやつを継承したクラスを作ることができます。新しく作るクラス名はCIntUnitとすることにします。
Visual Studioのメニュー プロジェクトからクラスの追加を選択します。
表示されたダイアログのクラス名にCIntUnit そして、基本クラスにCAccess_Specifiers001と指定します。継承しない(派生しない)クラスを作るときは基本クラスの欄は何もいれないでクラスを作ることができます。 プロジェクトからクラスの追加を選択します。
そうすると、CIntUnitは派生したクラスになり、継承と言う技術を使っていて、継承の基本クラスにCAccess_Specifiers001だけが選択されていることになります。クラスの定義は以下のように自動生成されます。
IntUnit.h
#pragma once
#include "access_specifiers001.h"
class CIntUnit : public CAccess_Specifiers001//★5.継承
{
public:
CIntUnit(void);
~CIntUnit(void);
};
★5.の部分で定義されたクラスは派生していないクラスと違って、上記の3行目のようにclass CIntUnit : public CAccess_Specifiers001 としてクラス定義がなされます。これがCAccess_Specifiers001を継承した派生クラスCIntUnitを定義する方法です。
通常の定義したクラスと違って、CIntUnitはCAccess_Specifiers001の機能をすべて持っていて、protectedおよびpublicで定義された関数や変数にはアクセスできるようになっています。CIntUnitは派生してるクラスなのでCAccess_Specifiers001のメンバ変数m_nValueを1000倍してくれる関数が使えます。実際にやってみましょう。まずCIntUnitにCAccess_Specifiers001のmf_m_nValue1000x()を呼び出す関数mf_1000x();を定義してみます。
IntUnit.h
#pragma once
#include "access_specifiers001.h"
class CIntUnit :
public CAccess_Specifiers001
{
public:
void mf_1000x(void);//★6.protectedで定義された関数を使うクラス。
CIntUnit(void);
~CIntUnit(void);
};
★6.のようにmf_1000xという関数を作りました。
IntUnit.cpp
#include "stdafx.h"
#include "IntUnit.h"
void CIntUnit::mf_1000x(){
mf_m_nValue1000x();//★7._tmain関数では呼び出せなかったが、派生クラスからは呼び出せる。
}
CIntUnit::CIntUnit(void)
{
}
CIntUnit::~CIntUnit(void)
{
}
CIntUnit.cppのプログラム部分にmf_1000xの処理として、_tmain関数から作ったクラスでは直接呼べなかったmf_m_nValue1000xを呼び出すように★7.部分のように記述しました。このように派生したクラスからはprotectedでアクセス指定子が宣言されている関数にできるのが派生クラスの権限です。そうするとCIntUnitクラスを使えばCAccess_Specifiers001クラスではできなかった処理ができることになります。このようにprotectedのレベルで定義された関数は派生クラスによって正しい使い方を定義してくれるのを期待して、直接呼べなくする関数や変数にする役割を持たせることができると考えてよいでしょう。
具体的には以下のようなプログラムになります。
sample_main.cpp
#include "stdafx.h"
#include "Access_Specifiers001.h"
int _tmain(int argc, _TCHAR* argv[])
{
printf("★アクセス指定子\n");
int nValue;
CAccess_Specifiers001* CAccess_Specifiers001_Instance = new CAccess_Specifiers001;
CAccess_Specifiers001_Instance->Set_m_nValue(120);
nValue = CAccess_Specifiers001_Instance->Get_m_nValue();
printf("%d\n",nValue);
//CAccess_Specifiers001_Instance->mf_m_nValue1000x();★8.protectedのメンバ関数なので呼び出せない。
nValue = CAccess_Specifiers001_Instance->Get_m_nValue();
printf("%d\n",nValue);
printf("CIntUnit派生クラス\n");
CIntUnit* CIntUnit_Instance = new CIntUnit;
CIntUnit_Instance->Set_m_nValue(240);
nValue = CIntUnit_Instance->Get_m_nValue();
printf("%d\n",nValue);
CIntUnit_Instance->mf_1000x();//★9.派生したクラスからだと呼び出しが出来る。
nValue = CIntUnit_Instance->Get_m_nValue();
printf("%d\n",nValue); return 0;
}
★8.は呼び出せませんでしたが、★9.からは呼び出せます。今回のサンプルだと、なぜprotectedにしたのか明確な理由が見いだせていませんが、例えば、派生クラスではm_wcUnitを使って単位文字列の管理をしてくれることを期待しているとしたら、基本クラス設計者の期待を裏切っていることになるやもしれません。今回は自分であえて、基本クラスの理念を守らなかっただけなので、雑な派生クラスを作ったというところになります。このようにアクセス指定子には意味があるし、派生クラスに期待されていることもいくつかあるということになります。といわれても英語の文書しかない基本クラスが配布されたとして、こうやって作られた基本クラスの意図していることを、素人プログラマが理解することは、なんつうか無理があるので、使い方がわからないままにおわったり、クラスを使うことに縛られて、どうしたらいいのかわからなくなるということが起こります。こんな感じでアクセス指定子が複雑に設定されているだけで、なんとなくクラスの設計時に期待されていることがあるということがわかっていただけたら、それでこの項目で言いたかったことは伝わったように感じます。
他にもクラスの定義で使われるいろいろなメカニズムによって基本クラスの設計者が派生クラスを生成したときの使い方で期待していることがなんなのかを予想することができるし、それを予想しなければ、うまく派生クラスを使いこなせないし、クラス自体も使いこなせないということに繋がります。もちろん、今回のサンプルのように意図していない使い方で無理やり使って、やりたいことが実現できれば、それはそれで問題ないわけです。
そんな感じで無理やり動かすように作成したプログラムの動作結果は以下のようになりました。見事に1000倍されています。派生したクラスは基本クラスの機能のすべてをひきついでいるということを理解しないと、sample_main.cppでのCIntUnitの使われ方について、よく意味がわからなかったかもしれませんが、アクセス制御についてなんとなく理解はしてもらえたかなと思います。
出力結果
★アクセス指定子
120
120
CIntUnit派生クラス
240
240000
もうひとつ、アクセス指定子に関して、補足しておかなければならないことがありまして、★5.部分のように継承をするときにもアクセス指定子を使うことができることについて触れなければなりません。★5.部分では以下のように継承したクラスの宣言を記述しました。
class CIntUnit : public CAccess_Specifiers001{
…
};
このように
class (新規派生クラス名) : (アクセス指定子) (基本クラス名)
のようにして継承が定義できます。アクセス指定子は省略することもできます。省略した場合はprivateと同じになります。今回のサンプルプログラムの場合は省略するとコンパイルエラーになります。これはどういうことかというと、継承時に記述するアクセス指定子は基本クラスの中のアクセス指定を一括して書き換えて、継承(すべての関数を引き継ぐ)するために、基本クラスの中のメンバ変数やメンバ関数のすべてがpraivateのアクセスレベルになって派生クラスで利用できるようになります。せっかく引き継いでも、アクセスできないので、_tmainから利用に関しては基底クラスの変数や関数において、何も使えるものはないといってよいと思います。派生したクラスではprotectedとpublicの関数や変数は使えます。
今回のように★5.の部分でpublicが指定されている場合は、アクセス指定子は基本クラスの内容と変わらずに使えることになります。したがって、_tmain関数からも基本クラスの変数でpublicが定義されている変数や関数を呼び出すことができるためSet_m_nValueにアクセスできます。publicが指定されているからと言って格上げされることはないため、privateやprotectedの変数や関数は呼び出しできません。基本クラスで指定したアクセス指定がそのままだということです。
★5.の部分でprotectedが指定された場合は、publicで指定されていた部分がprotectedのレベルに引き下げられ、やはり_tmain関数からは呼び出しができなくなります。派生クラスからは、publicとprotectedの両方のアクセスレベルのものへ呼び出しができます。
以上でアクセス指定子の説明は終了です。ここのサンプルで登場した1000倍したり1000分の1にしたりして単位を制御しようとか考えたクラスはこのまま放置するかもしれませんし、ひょっとしたら、このあとの記事で使いまわしてみるかもしれません。あまりサンプルに意味をもたせると、プログラムの内容にばかり理解力が奪われ、本来、簡単に深く知りたかったはずの事柄がないがしろになる恐れもあるという。どうしようか迷ってます。プログラム技術の説明って、いろいろと難しい。時間が有り余っている人は、単位変換まで含めてものすごいクラスを作っちゃってください。速度[m/s]* 時間[s] = 距離[m]とか、全部もうらすると科学技術計算が少しわかりやすくなるかもです。既にあるかもしれませんが。たかがサンプルでそういうことをしようかと思ったりして、壮大すぎる(汗。
C++に戻る