C++を客観的に切る
copyright 1995
Kubo
こうすれば良くなるC++(friend,static,inline,private,virtual)
NetNews発言を再編集しました。
fj NetNews--> fj.comp.lang.c , fj.comp.lang.c++
文章の繋がりが変かもしれませんが想像力でお読み下さい。
friend
- C++の変な仕様にfriendがあります。
世間では、friendを効率化の名目とともに容認する風潮がありますが、はっきり言って設計をしない人達
の戯言にすぎません。
きちんとした設計を施し、更にC++のプログラム仕様に落とせばfriendなんて
出てこないはずですが?
さて、その真偽は?
- 思うに、friendを使用せざるを得ない局面が生じること自体、既にアプリケー
ション作成者の設計ミス・能力不足だと思います。(継承されているアプリケー
ション・顧客の絶対要望等の場合は除く。あくまで新規で自分の裁量がきくアプ
リケーションの設計)
「動作効率の優先」から friend
を使うは違う。「オブジェクト指向でCみた いに使える」から C++
を選んだのも違う。
#あくまでプロの人を対象に意見しています。
#むしろ趣味の人の方が friend
を使わない事が「挑戦的」で良いのでは?
#その気ならいつでも使えるでしょ?
で、まず、システムの目的があって後から言語が付いてくるのものです。後工
程でらくをしたい、ヘボなコーディングを人にさせたくない、システムを記述し
やすい等の理由で C++ を選んでいるはずです。
「動作効率の優先」なら(例えば)Cの関数を呼べば良い。(ライブラリー化
してあること)「でもC++はそう言う言語でしょ?(Cの使用法もできる)」と考
えるのではなく、「設計そのものがちゃんと(例えば)オブジェクト指向」なら
friend
がでてこないでしょ?言語が先にあってその上のシステムも言語に合
わせて設計するなんておかしいです。
- 効率化に使うのか?
friendを使用しないプログラムが非効率の様に言われますが、効率を最大限に
考えて設計するのは当たり前です。
このように実装レベルの話と設計レベルの温度差が論議の的です。C++は設計
レベルで話をしなければいけません。
そして、綺麗な設計ができる人は「その辺の」設計者より効率がいいはずです
。効率=早い動作・早い対応・早い開発・早いバージョンアップ。
設計で難しいのは以上のバランスです。私も昔はこの辺で悩みました。
動作が早くトリッキーなロジックの追求で保守のみを重要視する設計者と良く
対立しました。
C++では最初に設計する人が重要です。ここで以降の命運が決まってしまいま
す。この最初の人が後のことを考えず、その場限りの設計(設計は無いのか「プ
ログラム」ですね)をすると言語の特性から問題を残したままになります。
また、friendは本当に効率的なのでしょうか?
設計は手段であって目的ではないはず?
目的が近いところ(統計を取るとか、ちょっとシュミレーションするとか)だ
とおっしゃる意味に近くなります。
しかし、設計には様々なアイディアを含んでいます。はっきり言ってほとんど
これ。
「一緒にヂュエットしてくれるプログラムを作って」と言う漠然とした問題提
起をお客さん、あるいは自分で持ち出すと、実現の手段として、アイディアとコ
ンピュータ的な設計の間を思考が駆けめぐるはずです。
言語・機械の限界とアイディアのすりあわせ。新しい発想は限界を知る事から
始まると私は思っています。そして、限界の検証が設計なのです。
プログラム的な設計は翻訳。
コーダーは代筆。
私の言う設計は上記の全てを含んでいます。
最初の設計思想を後世に継承できる可能性のある言語体系をC++は持っている
と思います。もちろん柔軟性も重要です。
friendを使用しないと非効率だとは、たぶん実行時の(速度)事を言っている
と思うのですが…。
それともまわりの設計者が付いてこれないと言うことかな?
または、
main(){
あれこれ
}
ですむところを
virtual Main::Initialize(){}
virtual Main::Running(){}
virtual Main::Terminate(){}
Main::Main(){}
virtual Main::~Main(){}
//
main(){
Main* p = new Main;
p->Initialize();
while( p->Running() );
p->Terminate();
delete p;
}
と基本クラスをそろえるのが面倒なのかな?後工程ではMainを派生させた方が
効率いいけど?(これくらい皆さん持っていますよね?)
- 便利だから使うのか?
「便利だから使う」にはこんな議論もあります。
その昔、「goto を使わないで設計する」の是非がありました。
for( ..... ){
for( ..... ){
if( ... ){ f = TRUE; break; }
}
if( TRUE == f ){ break; }
}
と言うように内側の for 文内の if 文から一気に for
を抜けたい場合。「か
えって可読性・設計上無理があるのでは?」と言う意見。
goto が使えれば、
for( ...... ){
for( ...... ){
if( ... ){ goto LoopEnd; }
}
}
:LoopEnd;
の方が美しいのでは? と言う意見。
しかし、一つの例外を容認するとシステム全体が美しくないです。だから、シ
ステムに関わる人全員にはgotoを使用しないよう注意します。
その前に設計が悪い可能性があります。良い設計とは、ちょっと頭で考えれば
想像(日本語でシステムが浮かぶ)させやすいようになっています。
- 言語としてのfriendはどうでも良いことです。(そう言う言語なんでしょ?)
小規模で開発をしている分には好きにすればいい。ただし、プロ?として開発
するなら、言語的にまだ不満がある。(可能性はある)
チームを率いて開発している人なら、何十年もこの事を解っているはずです。
その上で、プログラム法(設計法)もフロー化・構造化・カプセル化・抽象化・
再入化と模索が続いてきました。
言語も併せて進化してきた。(昔は実現がめんどくさい)まぁ、この部分も曲
折して述べていますが。
その上で、例えばfriendを使う場面とはどういう場合か?を問いたい。
例えば、私はfriendを使うならCの関数(ライブラリー)を使う(作る)。
ここで勘違いが起こるのか、Cの関数=C++でも作成できる。と考えられてしま
うと、困るのです。(現にC++で作ります)
クラスを作成しているのに、そのクラスに依存するfriendを作る。(なんだそ
れ?)
あくまでfriendになり得る関数は、単独で完結する関数。(Cのライブラリー
)
ただし、fopen、fcloseの様なライブラリーはちょっと違うと思う(議論ある
)。
friendを使用するなら、Cのライブラリーとして作成し、ライブラリー仕様書
に登録すればよい。
ただ、設計を含めて議論しないと、言語のみの議論になり、堂々巡りになって
しまう。監視をしていないと下の設計者やプログラマが勝手におかしなコードを
書く。つまり、「そう言うことができる言語」だから「する」のです。
だからさせないように規制をする。これはC++のみに限りません。
あくまで言語を使用するのは人(設計者・プログラマ・コーダー)、そして、C++
を使用して開発するなら、考えに各人濃度が生じる。ある人はCの部分から脱
却していない(Cは関数言語であることも解らない。これ以外と多いのです。サ
ブルーチンと勘違いしている人が)、ある人はOOPで考えてしまう。
この垣根はチーム開発を行うと非常に深刻です。そして、基になる設計を担当
する人は「できる人」で(そうでない場合のプロジェクトがどんなに悲惨なこと
になることか)、最終的な完成は全てのバランスにおいて考慮されています。
- friendはディバッグに有効?
処理系にもよりますが、私の場合は、条件コンパイルを使用してます。(以下
参考までに)
class ClassA: ClassB{
ClassC c;
int d;
.....
.....
#ifdebug
virtual void DebugWrite( fileClass& file ){
file.Begin( "----Class A----" );
ClassB::DebugWrite( file ); //継承基のDebug
c.DebugWrite( file ); //持ちクラスのDebug
file.Data( c ); //intデータ
file.End( "----Class A----" );
#end
};
として、現在のクラスデータをファイルに保存。ほとんどのクラスに実装。
class fileClass{
fileClass( fileName* );
};
使用時
.....
ClassA a;
a.Read();
#ifdebug
file.Begin( "----Begin----" ); //title
fileClass file( "debug.log" ); //格納場所の指定
a.DebugWrite( file ); //ClassAはdebugせよ
file.End( "----End----" ); //区切り
#end
仕様の基本はクラスに動作させる事です。つまりクラス自らがアクションによ
って自身をdebugする。
これをミスするとクラス構造に破綻が生じる。(Debugに限った話をしていま
せん)
結果。
-----Begin----
>-----Class A----
>>-----Class B----
%%クラスBのデータ
<<-----Class B----
>>-----Class C----
%%クラスCのデータ
<<-----Class C----
%クラスAのデータ(int a)
<-----Class A----
-----End----
利点。
全てのクラスでDebugWriteが実装して有るので好きなところでクラス内部が見
渡せる。(元クラスを呼ぶだけ)
設計思想が保てる。(耐久力が強い)
当然ソースの書き換えは無いです。一度できたクラス資産の再利用が簡単。
欠点。
継承過程、持ちクラスのDebugWriteも呼ばれるので Debugログが大きくなる。
新規コーディングがめんどくさい。(ちょっとしたクラスも実装するので)
拡張として。継承と持ちの判定を入れるとさらによいかも。私は他に
virtual BOOL IsObject();
を実装し、クラスの自己調査をさせています。(演算後に
データが基準を満たしているか?)に使用。
素人プログラムではここまでコーディングしないでしょうが、これが(debug
の考慮等)仕事のプログラムです。
結局、後工程が楽なのです。プログラム後のDebugは仕事のうちに入らない(
自論)。つまり、こんな事でも最初に設計をきちんと行う。
VisualC++では DebugTraceを作ります。内部はDebugWriteと同じでTRACEマク
ロに置き換えるだけ。
- friendを使わない快感
設計段階で「オブジェクトの連携」が最終的に収束していく様は快感ですね。
(マゾっぽいですね)
途中で設計を妥協した人に「どうだ!(私はやった)」っていう快感もある。
納品して、理解できてない相手の技術者に演説する快感もある。(これはサゾ
?)
少しくらいこういう快感を楽しんでもいいでしょ?(プログラムを長く続ける
秘訣?)
「使う使わない」は個人の問題です。ただし、自ソースの継承やプロジェクト
の継承を前提に考えるなら、使わない方がお得です。
もし使うなら膨大な仕様書が必要になります。
ヘボなプログラムは「わざわざソースを解析」しないと使えないのです。
またヘボなコーダーは「何故かソースの解析」から仕事を始めます。
inline
- friend と同様に、inline も
class{
private:
int a;
public:
inline int Get( void ){ return a; }<---これ
};
と「わざわざ触って欲しくない(隠したい)のに見えるところに記述するの?
」です。
設計者は private
と隠すことを意図して設計するのですよね?そうすると inline
なんて必要ないのでは?
inlineがあるから…と言う意見は私にとって愚問。しかも「 int a;
」そ のものの文字も消したい。
これは、(例えば)private
は隠したいんですね。なら、本当に隠れる(ソー
ス上から)仕様。
つまり、設計者の言うとおりにコーディング(メンテナンスの継承)しなさい
。
その為に、private
記述者のセキュリティーを付加し、アクセス権が無ければ
ソースを見られない。
更に言うとクラス継承もアクセス権(セキュリティーの)を付ける。これによ
って、設計者がどの部分を継承用にしたクラス(public)かすぐ解るし、ヘボなコ
ーダーや設計者を追い出せる。(触らないでくれ)
隠したいから隠したい
- C言語の場合は言語自体制約が少ないので(語弊はあるが)以下のように対処
していました。( カプセル化と抽象化
を見よ)
開発用Header.h
struct A{
int x; //Windowの位置x
int y; //Windowの位置y
int work; //Windowのwork
};
そのソース.c
extern A* CreateWindow( void ){ return (A*)malloc( sizeof(A) ); }
extern void TermWindow( A* win ){ free( win ); }
extern void SetWindowX( A* win, int x ){ win->x = x; work = ****; }
extern void SetWindowY( A* win, int y ){ win->y = y; work = ****; }
こんな感じで、extern関数がC++のpublicに相当し、特に CreateとTermはコン
ストラクタとディストラクタに相当する。
このソースでできたObjはライブラリーに入れますが、ここで workを隠蔽した
い場合。
公開用Header.hは
struct B{
int x; //Windowの位置x
int y; //Windowの位置y
};
とし、また全て隠蔽したければ「BYTE*を使用する」で対処できました。
C言語では、クラスイメージを「一つのソース」単位に記述することで、カプ
セル化・隠蔽化・抽象化できました。まぁ構造体は先頭の場所と内部の位置だけ
の代物ですから…。
ざっと簡略化してしまいましたが、この例の意味理解してもらえるかな?
このようにきちんとプログラム(設計)していればCからC++に移行してもさ
ほど垣根がないのですが、多くのCプログラマ(強引に動かすプログラム
#作成 者)はオブジェクト指向に苦しんでいます。
C++は単純に上の例の方法を言語化したにすぎず、言い換えればCでも十分C++
ライクな設計はできるのですが、まぁC++の方が便利ですね。
話を戻して…。
ここで、先のPrivate部の隠蔽が問題になると思います。
「他人にクラスを提供したいがprivate部を隠蔽して渡したい」場合、最終的
な派生クラスを持たした公開用のクラスを作成するしか手がないのでしょうか?
ビジネスでプログラムしていると強く思ってしまいます。中には「新しい発想
の織り込められたソース」の隠蔽(知的所有権)とともに必要になります。
例えば、クラスにprivateなんて言う記述は無くし、
class A{
protected:
****
public:
****
};
のみにし、private部はソース(cpp)の中で、
class A::private{ int x; int y; };
と切り分けて宣言できれば便利だと思いますがいかがでしょう?欲を言えば継
承課程も隠蔽したい。
当然、メモリーの切り方に関して等に問題が出ると思いますが、あくまで例と
して。うまいアイディアがあれば更に良い。
現行の対処方法。隠したいクラス(ヘッダー)。
class A{ };
class B: A{ };
公開クラス(ヘッダー)。
class B; //宣言のみにしておく。AとBのヘッダーはソースに記述。
class C{
B* mClassB; //隠したいクラスはアドレスで宣言。以降にクラスBと同じ
様な関数を記述。
}
- 何故隠したいの?
私は現在フリーです。会社を持っていますが人任せです。
フリーである私は主に「企画ソフト(いわゆるサンプル)」のソフトを作成し
ています。これは、企業が製品にする前の発想段階のソフトで、「著作権」「特
許(なかなか降りないが)」は自分のものとし、できたソフトを企業に渡します
(入札)。
企業はその核を使用して製品にします。もちろん「契約」で「秘密厳守」の契
約をしますが、時間の無い場合に渡すソースは加工(隠蔽の)をしてない場合が
あります。
この場合、たいてい渡した企業の別ソフト(別バージョン)は若干クラスのソ
ース的変更を加えられ使用されています。まぁ、別に良いんですけど「何か損を
したような気分になる」ので、もっと「ソースそのものに隠蔽を持たせられない
かな?」と思っています。
また、クラス継承等ををわざわざかくして使用してもらうより設計の通り使用
してもらう方が汎用的なんですが、「とりあえず生活」もかかっていることだし
1・2年は食べさせてもらわないと。
インターネットのおかげでバーチャル企業が実現でき、会社と言う
#形態も必
要ないです。その時のアイディアを元にネット上で仲間同士プログラムを作りそ
れを製品にする(売り込む)。
現在は小規模ですがそのうち…。
メソドは黙ってvirtual
- virtualでなくてもいいじゃない?
開発を単独で行う(趣味の範囲程度のシステム)なら個人の割り切りで良いで
しょうが、解っていない設計者・プログラマが入ったり、自分でも1年前のソー
スを継承したり使用したりする場合、バグの危険性があります。
これはC++に限らず、CでもFortranでも言えますが。つまり設計が未熟。
C++ではvirtual宣言が追加された。使うなら全て使う。使わないなら全て使わ
ない。私はvirtualを両天秤にかけると使う方が便利(色々な意味で)なので使
います。
プロジェクトで使わないと使うが混在すると、例えばディストラクタでおかし
くなる。
知らない人のために…。
class A{....};
class B:public A{....}; で、
A* a = new B;
とすると、
delete a;
はディストラクタがvirtual指定でないとおかしくなる。
これは有名ですね。つまり、ディストラクタは必ずデフォルトでvirtualにし
ないとクラスの信用性がない。
他の関数も同じ問題が生じる。例えば、
virtual A::Running(){}
A::function(){
while( 1 ){
Running(); //この関数は継承後の設計者が記述する
}
}
と言うクラスが存在すると、継承語のクラスB。
virtual B::Running(){}
としないと最初の設計者の意図通りにならない。
C++を抽象データとしてとらえるなら、カプセル化に近い考えで止めないと後
の継承者に迷惑をかけます。
static宣言は再入可能でない
- クラス内宣言にstaticを入れると再入可能ではありません。
クラスは再入可能を保証してあるはずなので設計時には気を付けましょう。
特に、クラスの生成は順番通り行われるわけでないことを記憶しておきましょ
う。
この項は皆さんへの課題です。(何を言いたいかわかりますか?)
最後に
- friend,inline,virtual,staticの使用を容認する人の言い訳は、「効率的だか
ら」です。
はっきり言って素人の考えですね。「効率的」の意味を解っていません。
結局、自分の中の狭い範囲のプログラム(設計)しかできないのです。趣味の
プログラマとでも言いましょうか?
「設計とは何か」を見つめる必要があります。C++は設計と密接に結びつく言
語です。きちんとC++を使いこなすためには設計をきちんとできなければいけま
せん。
設計もできないのに「効率的」と理由を付けるのはおかしいことです。
戻る