オブジェクト指向の倒し方01/11 | 「オブジェクト指向の倒し方、知らないでしょ? オレはもう知ってますよ」

「オブジェクト指向の倒し方、知らないでしょ? オレはもう知ってますよ」

オブジェクト指向の倒し方を知っているブログ
荀子を知っているブログ


テーマ:
<冒頭-筆者の前提>
 
筆者の都合として
 
・Windows を前提
・Visual Studio ( Visual C++ ) を前提
 
であり、当著での「C++」や「OS」は、全てマイクロソフトの中の話とする。
 
当著は計画力、設計力の向上を目指したC++の設計論の考察を目的としており、筆者の都合と無関係と判断した従来の「正しい解釈」は無視する前提である。
 
ここでは「従わせる正しさ」や「認められるための結果の出し方」などの追求は一切しておらず、当事者にとっての妥当(適正)な計画や設計かどうかを重視する考察が中心であり、複式帳簿でいう貸方と借方の関係における、不足しがちな借方論中心を前提としている。
 
貸方論は成立追求であるのに対し、借方論は複合要因の実態追求であり、当事者にとっての妥当(適正)な予定債務(準備高)であるかどうかの地道で丁寧な事前事後の整合が、当事者の計画力・設計力の向上要因と筆者は考えるためである。
 
そのため当著では、C++の実態と向き合うことを阻害しているだけの証拠や規則や常識や効率といったものは完全無視した考察報告を目的としている。
 

<冒頭-筆者の都合>
 
ここではまずは「C++はC++(仕様は仕様)」「設計目的は設計目的」「規則は規則」としてそれぞれ区別(整合)していくことを強調している。
 
筆者は、規則(正しさ)を否定しているというより、適正責任(妥当性)と選択責任(選別性)の区別を阻害する主張や計画の仕方を否定している。
 
まず、設計目的や規則(正しさ)とは、最終的には各所・各当事者の裁量力と自治都合の自由問題、つまり道義的責任の自由問題と考える。
 
適正責任(妥当性)と選択責任(選別性)は、帳簿でいう借方と貸方の関係であり、これは環境力(下限力)と汎用力(上限力)の関係であり、準備高(実高)と残高(表高)の関係であり、自立的構想論(企画性)と強制的根性論(道義性)の関係である。
 
その区別(整合)を阻害しているだけの、ただ結論が速いだけの何の丁寧さ(慎重さ)もない雑な正しさとは、いわば貸借対照表(複式)に言いがかりをつけ、出納帳(単評)の数字の大きさだけを見て威勢良く目を吊り上げて騒いでいるだけの、そのうち信用事故を起こす無計画な正しさである。
 
当事者の適正性を尊重せずに、ただ多頭的な強制的根性論(道義的責任)の正しさだけで手当たり次第に打ちのめし合うことに神経質なだけなのは、常に何かに頼り切って常に何かのせいにしているだけの、肝心な時に何の役にも立たない無関心主義、無計画主義の拡散に過ぎない。
 
自立的構想論(当事者の信用都合)と強制的根性論(正しさ)の範囲は、それぞれ当事者本人にとっての妥当な範囲というものがあるはずであり、どのような理由でいくら何かのせいにした所で、その範囲を選ぶのも疑問をもつのも最終的には当事者本人である。
 
主張や計画がもし、当事者に妥当(適正)な上限下限の債務回収の準備高であるか、当事者のその信用都合を第一に追求する基本が重視されているのなら、当事者に合わない不健全な正しさ(当事者に合わない証拠や成功例)を強要し合う弊害は、次第に費用分解で分類(集約償却)されていくはずである。
 
大衆的な貸方論中心の発想とは、頂点と頂点の良さで結んだ威勢的な減点洞察であるのに対し、筆者のいう借方論中心の発想とは、低点と低点の最低限を結んだ地道で丁寧な得点洞察である所に大きな違いがある。
 
筆者の借方論中心の観点による、従来のC++の見方に対する疑問から、「模範例」という正しさの都合で機械的に並べているだけの証拠や成功例は完全無視し、「実態例」という当事者の都合に良い考察ができるための機械技術の調査報告(C++の仕様)と、人的基本の考察報告(設計)を、まずは当著では目的としている。
 
多頭性に頼って人を巻き込むことから始めるのではなく、必要不要の関心度合いはそれぞれの当事者の診断から始め、そこから当事者本人にとって不足に思うことがもしあれば名義力や裏書力(当事者力)をもって、より妥当(適正)な「実態例」を「報告し合う」ことが健全と考える。
 
無関心者同士の打ちのめし合いに過ぎない「教え合い」を否定し、史学研究の世界のような関心者同士の「報告し合い」を筆者は重視しており、自分にも関係があるかも知れないと思った当事者の考察に協力したいという意図から、これまで全く触れられてこなかったC++の実態的な調査と、実態的な考察報告を第一目的としていることを強調しておく。
 

<冒頭-理解の難易度と調査報告の順番>
 
当著では、C++の仕様の構造を報告するために機械語を用いている部分もあるが、それはC++の仕様の理解のために過ぎない。
 
実際に64ビットのコンパイルでは、2018年時点でも機械語の直書き機能(インラインアセンブラ) _asm は廃止されている。(C++とはいったん分離されている MASM(マクロアセンブラ)の方とで、間接的に使用することは可能のようである)
 
機械語ありきは32ビット限定になってしまうため、基本的には64ビットで通用する解説を目指している。
 
筆者は機械語の専門家でもなければC++の専門家でもない。
 
どれも初歩的な報告に過ぎず、高度な専門知識がなければ理解できないような解説はしていない。
 
ただし、C++は永らく模範に終始した解釈が繰り返されてきたことに対し、当著では唐突に実態重視から入っているため、そこに困惑する人は多いかも知れない。
 
当著はまずは<仕様調査>という観点の報告から入り、その認識力を示した上で<設計考察>に入るという形を採っている。
 
<仕様調査>では少々荒い解説が多いため、早とちりしないよう注意が必要である。
 
筆者は<仕様調査>においては機械技術的だが、<設計考察>の方は人的基本の考察を中心としている。
 
<仕様調査>の方では、設計方法ではなく「C++の仕様の実態」を報告することが中心で、繰り返すが「C++の仕様」「設計」「規則」はそれぞれ区別(整合)するべきであることを強調しておく。
 
従来のC++の解釈方法は、そこを何も区別(整合)せずに全て規則で一本化することから始め、根拠のない規則を機械技術だと見なそうとし、C++の仕様の解釈に人的基本を求めるような破綻した思考で完全停止してしまっているため、そこが筆者の指摘の構図となっている。
 
そもそもC++とは、機械語という低級言語の良さを活かしつつ高級言語化を求めている前提なのだから、人間の視点から見てC++の仕様が荒く見えるのは当然なのである。
 
その実態に向き合おうともしない従来のC++の解釈方法は「正しく+解りやすく+早く」というありもしない、できもしない幻覚を競うことに神経質になるばかりで、ただ結論の速さをだけを求めただけの無計画な「正しい道具の使い方」に終始するのみである。
 
いくら正しさが強調された所で、実態を無視している以上は雑で荒いだけの手抜き設計でしかなく、それはいわば貸借対照表(道具の使い方)の勘違いの態度が、そのまま経営計画(目標設計や目標規則)の勘違いの態度なのと同義である。
 
当著の<仕様調査>の報告が荒く見えるのは、筆者の考えが荒いからではなくそれがC++の実態であり、C++の仕様が機械技術的だからに過ぎない。
まずそこに向き合わなければ、設計の適正性(妥当性)や、またC++自体の今後についても、何も見えてこなくて当然である。
 
<仕様調査>だけを見ていると筆者はまるで機械語至上主義者のように見えるかも知れないが、<設計考察>の方まで通して一読してもらえばそうとは限らないことが判ると思う。

<冒頭-用語について>
 
・class 及び struct で設計される自由型は「型」と呼ぶ。自由型の変数化(インスタンス)は「型変数の生成」等と呼ぶ
・関数は「手続き」や「プロシージャ」と呼ぶ
・メンバは主に「所属」と呼び、ややこしくなった時は「型の所属変数」や「型の所属手続き」等と呼ぶ
・bool、BOOL、char、BYTE、short、WORD、long、DWORD、int 等は「基本変数」や「標準変数」等と呼び、
 ここの差や違いは「型違い」ではなく「バイト数違い」という表現を主に使う
・Cの関数は「Cの手続き」( cdecl )、外部関数は「外部手続き」( _stdcall )と呼ぶ
 クラス関数は「型手続き」( thiscall )と呼ぶ
・アドレスは「番地」と呼ぶ。詳細は順次
・ポインタは「番地変数」と呼ぶ。詳細は順次
・文表現で「C++では」を多用しており、それは「Cでは」も含む場合が多いがそこは省略している
・文表現で「のちほど記述」という意味で「次述」や「順述」は近い所で、「後述」は離れた所でという意味で使い分けている
 

<仕様調査-変数の番地について>
 
変数という存在を整理する。
 
ここでの解説は、普段は支障はでない話だが、変数番地と領域番地の違いを知っておくことは損ではなく、また筆者の主張をともなうため、解説する。
変数の番地は、変数番地と、領域番地と2つある。
 
例:
int iValue01 = 100;
int iValue02 = 200;
int *pValue01 = &iValue01;
int *pValue02 = &iValue02;
 
この pValue01、pValue02 に入る番地は、iValue01、iValue02 の変数番地ではなく領域番地である。
 
C++では、変数番地は指定はする(できる)がそれを取得する手段がないらしい。
 
そのため「変数番地も取得できるようにして、変数番地で参照(取得や代入)をできる仕様にした方が良い」というのが筆者の主張である。
 
筆者の主張を表現すると
 
例2:
int iValue01 = 100;
int iValue02 = 200;
int *pVal01;
int *pVal02;
void **pAdd;
_asm
{
    push eax
    lea  eax, [ pVal01 ]
    mov  dword ptr [ pAdd ], eax // 領域番地ではなく、変数番地を代入
    pop  eax
}
// pAdd の中にではなく pAdd が指す変数番地 pVal01 の中に、iValue02 の領域番地を入れている
*pAdd = &iValue02; 
_asm
{
    push eax
    lea  eax, [ pVal02 ]
    mov  dword ptr [ pAdd ], eax // 領域番地ではなく、変数番地を代入
    pop  eax
}
// pAdd の中にではなく pAdd が指す変数番地 pVal02 の中に、iValue01 の領域番地を入れている
*pAdd = &iValue01;
 
であり、これが「変数番地で変数領域を書き換える場合」の例である。( lea は位置番地を、mov は中身の取得)
 
pAdd を **(多重)にしているのは、番地変数が指す番地の中を参照、変更するため。
 
なおこの方法は、基本変数の中へ基本変数でない領域番地を入れることや、型変数の中に基本変数の領域番地を入れることも可能だが、意味は伝わったと思うのでここでは省略する。
 
C++で取得や代入によく使われる番地は、厳密には領域が確保された場所であって変数の場所ではないことが、変数番地と領域番地の違いである。
 
64ビットでは _asm が使えないため、64ビット以降はこの方法は実現不能だが、もし変数番地を取得する概念が標準化されれば設計の幅が広がるだろう。
 
もし64ビットで _asm の記述が可能であれば、番地変数は基本8バイトが前提となるため、
 
_asm
{
    push rax
    lea  rax, [ pVal02 ]
    mov  qword ptr [ pAdd ], rax
    pop  rax
}
eax の所は rax に、dword の所は qword という書き方になる。
こうした理由から当著では、変数の領域を基本的には「変数番地」とは呼ばず、「領域番地」や「型の領域番地」等と呼ぶことにしている。
 

<仕様調査-変数領域の確保の仕組み>
 
型(クラス)は、仮想の仕様を使用していない型と、仮想の仕様を使用している型とで大別でき、領域の形に少々違いがある。
 
ここからさらに、結合を用いている場合と、用いていない場合とでその事情も異なってくる。
 
仮想仕様の場合については後述とし、仮想仕様を用いていない場合の、基本的な仕様から順番に解説していく。

設計側
class ClassA
{
public:
    int   iValue;
    char  chValue;
    short sValue;
};
 
利用側
ClassA clsA;
size_t stSize = sizeof( clsA ); // 確保されている領域のバイト数を取得。12 が入る

この例では、int 4バイト、char 1バイト、short 2バイトの「合計7バイトの変数領域を確保する型」のように見えるが、実際には合計12バイト確保される。
 
char そのものは1バイトでも、内部的には4バイトの領域として確保され、通常使う分には4バイト中の1バイト目だけの使い方がされる。
 
これはコンパイルの標準仕様であり、4バイト以内の変数は、変数1つに付き内部的には4バイトずつ確保されていく仕様になっている。
 
これを変数のバイト数に合わせたい場合(この場合における計7バイトの概念にしたい場合)は、コンパイルの設定で変更することもできる。
 
しかし特に理由がなければ、通常は標準のままで考えておいた方がいいかも知れない。
 
この標準仕様の理由はよく解らないが、領域参照を基本4バイト単位( dword ptr )で一貫しておくことの機械語約後の効率化が狙いかも知れない。
 
なお64ビットの場合でもこのコンパイルの標準設定は全く同じで、4バイト以内の変数については、1バイトの変数、2バイトの変数であっても内部的には4バイト単位で確保される仕様になっている。
 
当著ではこの標準設定の仕様を前提とし、また解説のややこしさを省くために、基本変数は以後4バイトのもの( long、DWORD、int 等 )を多用して解説していく。
 

<仕様調査-番地変数の意味>
 
C++では、どんな番地変数であっても、番地変数である以上は一律で32ビットの場合は4バイト、64ビットの場合は8バイトの、番地を入れるための領域として確保される。
 
設計側
class ClassSpace{}; // 中が空の実験用
class ClassPoint
{
public:
    void       *pvoid;
    char       *pchar;
    int        *pint;
    ClassSpace *pcls;
};.
 
利用側
size_t stSize = sizeof( ClassPoint ); // stSize に 32 Bit だと 16 が入り、64 Bit だと 32 が入る。
 
ClassPoint の例では、32ビットだと番地変数1つあたり4バイトが4個で合計16バイト。64ビットだと番地変数1つあたり8バイトが4個で合計32バイトになる。
番地変数は、どんな番地変数として確保しても、根は4バイトまたは8バイトのただの番地変数であるという例を次に示す。

設計側
Class001
{
public:
    int iVal01;
    int iVal02;
    Class001()
    {
        iVal01 = 100;
        iVal02 = 200;
    }
};

利用側
Class001 cls;
Class001 *pclass = &cls;
void     *pvoid  = ( void * )&cls;
int      *pchar  = ( int  * )&cls;
int iRet;
iRet = pclass->iVal01;                   // iRet に 100 が入る
iRet = ( ( Class001 * )pvoid )->iVal01;  // iRet に 100 が入る
iRet = ( ( Class001 * )pchar )->iVal01;  // iRet に 100 が入る
 
この例では Class001 の領域を確保し、番地変数を使って iVal01 を参照する例だが、3つの取得方法は内部的(機械語的)な動作は全く同じである。
 
番地変数を確保する時に指定する変数や型は、予定している参照の型を指定しているだけで、いってみれば参照の時のキャストを省くためだけの指定である。
 
この仕組みは番地変数ではない基本変数の場合でも内部的には同じだが、C++の記述上では番地変数の方がそれがより特徴的である。
 
番地変数はどんな形で確保しても、根はただの番地変数である。

例:設計側
Class001
{
public:
    int iVal01;
    int iVal02;
    Class001()
    {
        iVal01 = 100;
        iVal02 = 200;
    }
};
 
利用側
Class001 cls;
int  iVec[] = { 0, 1, 2, 3 };
BYTE byhEnc = 255;
int  iCate[3];
void *pList[3];
pList[0] = ( void * )&cls;    iCate[0] = 0;
pList[1] = ( void * )&iVec;   iCate[1] = 1;
pList[2] = ( void * )&byEnd;  iCate[2] = 2;

これは、番地を保管する void * の番地変数の配列 pList[3] に、それぞれ不特定の番地が入っていくという設計例である。
 
どんな番地が入っているかを後で判別できるように iCate を使い、Class001 の番地なら 0 を、int 配列の番地なら 1 を、単体の char なら 2 を、というように後で識別できるようにしておくというように、想定次第でいくらでも設計できる。
 
繰り返すが、番地変数はどう確保しても根は番地変数であり、種類の指定の利点はキャストの省略だけである。
 
「番地変数を参照する種類が1つしかない想定なら、それに合わせておき毎回キャストする手間を省く」という概念がよく使われるに過ぎない。
 
保管方法の想定がしっかり設計されていれば、どんな種類の番地変数にどんな番地が入っていても、入れても何の問題もない。
 
この番地変数の話は全体的にはこれであっているものの、C++の特性として注意する点が2つある。
 
1つは「型の結合関係と関係ある場合の自動番地変更」の仕様、もう1つは「手続き番地」の話も含めると若干ややこしい注意点がある。
 
その2つについては後述する。

ほうえつさんをフォロー

ブログの更新情報が受け取れて、アクセスが簡単になります

Ameba人気のブログ

Amebaトピックス