Programming Tips 1024
プログラムしているときに思いついた小ネタを書きます。このコーナーへの投稿もお待ちしてます^^
特にことわりが無い限り、C++/VC++/Win32です。情報提供者orネタ提供者がいる場合は、表題の右横に《 》で書いています。
'01/03/04
Tips 1. コメントアウトをコメントアウトする
/* 〜 */でコメント化したりしますよね。
/* MakeInstance( ); TestApplication( ); */ |
これを一時的にコメントアウトしているの解除したいのですが、それは3文字同じ文字を加えれば出来ます。
//* MakeInstance( ); TestApplication( ); //*/ |
急いでるときには結構便利です^^
Tips 2. コメントアウトしている上からコメントアウトする
また、/* */でコメントアウトしている部分を含めてコメントアウトしたいとき、/* */のネストを許していないC++の処理系だと非常に困ります。
MakeInstance( ); /* TestApplication( ); */ |
こういうときは、こう。
#if 0 MakeInstance( ); /* TestApplication( ); */ #endif |
#if 〜 #endifはネスト可能(なはず)なので、これならいくらでもネストできます。
Tips 3. 変数のスコープを限定する
for(int i =0; i<n ; ++i ) などと、宣言した変数iのスコープが、for文の外まで出ている処理系があります。少し古い仕様に基づくCコンパイラはすべてこれで、VC++もいまだにそうなっています。(というかコンパイルオプションで変えることは出来るのですが、そうするとWINDOWS.Hがコンパイルを通らないという…)こうなっている利点は、よくわかりません。私は、以下のようなスタイルでプログラムしています。
{ // ここにコメントを書く for(int i=0;i<n;++i){ x[i] = n; } } |
こうやれば、無意味な変数名の衝突は避けられます。また、先頭にコメントを書いておけば、コメントの有効ブロック(笑)がわかって便利です。他人のソースを見ていてよく思うのですが、コメントの有効範囲がわからないのです。そのコメントはどこからどこに対するものなのか。それを防止する意味でも、こういうスタイルもアリかな〜と思っています。
Tips 4. }はデストラクタ呼び出し記号である
} はスコープエンドを意味します。つまり、(気づいていない人が多いですが)そのスコープ内にある変数のデストラクタを呼び出すための記号とも言えます。よって、デストラクタの存在しないprimary data type(intやcharなど)ならば良いのですが、これがユーザー定義型で、デストラクタが異様に重いとループにしたときに痛い目に遭います。
たとえば、forのループ内で宣言した変数のデストラクタは、そのループ回数だけ呼び出されます。これが嫌な場合、その変数をループ外で宣言するしかありません。そうすると変数名が衝突したりして、またうっとーしいのですが(笑)
Tips 5. ローカルスコープで宣言している変数はいつ確保されるか?
変数はコンストラクタが呼び出される寸前にメモリ確保されると思っている人が多いですが、そうなっているコンパイラは稀です。そんな実装にしてしまうと、for文中で宣言された変数に対するメモリ確保〜解放が、すごい回数必要になるからです。
よって、変数のメモリの確保は、関数のスタートアップルーチンのなかで一気に行なわれます。たいてい、使用している変数の総サイズ分だけヒープから確保(=ベーススタックをずらす)する作業になります。逆に解放は、関数の終了処理ルーチンで行ないます。
コンストラクタ、デストラクタとはまったく違うフェーズでメモリの確保/解放が行なわれるということは知っておくべきでしょう。
Tips 6. 変数の命名規則はなぜ必要か?
ハンガリー記法では、メンバ変数ならば接頭辞としてm_で始めたり、グローバル変数ならばg_をつけたりしますね。
私の場合、あまり気にせず変数を命名していますが、とりあえずメンバ変数とかぶることはなくなるので、そういう意味では非常に便利です。しかし、変数タイプとして、整数ならn だとか、文字列なら szだとか本当に必要でしょうか?
そのへんは、やや疑問もあります。しかし、それより本当に重要なのは、配列の場合のa(arrayなので)だと思うのです。配列をnewしたら、deleteするときはdelete [ ] しないといけないという、不条理な仕様(笑)に対抗するため、配列の変数名には必ずaをつける習慣をつけています。そうすれば、deleteするときに [ ]を忘れないと。
しかし、これもauto_ptrやauto_arrayを使い、実際にdeleteやdelete [ ]なんて使わないほうが安全であることは言うまでもありません。
Tips 7. クラスメンバを直接参照しない
オブジェクトコンポジション(よーするにいくつかのクラスを肉団子のようにくっつけて一つのクラスにすること!)していると、そのコンポジットしているメンバを、
m_vBGMManager.Set ( ... ); |
のようにして呼び出すのは避けたほうが良いです。というのも、このクラスは、BGMマネージャーに対してアクセスできればいいだけであって、BGMMangerをこのクラスが所有する必要は無いかも知れないからです。要するに集約関係にあればいいだけで、所有については問題としないことのほうが多いのです。
よって、少し面倒でも、これのポインタを取得する関数を作って、それを間接で呼び出してやるというのがひとつのマナーだと考えます。
CBGMManeger*GetBGMManger( ){ retun&
m_vBGMManger; } として、 GetBGMManager( )->Set(...); |
ですね。
Tips 8. メンバ変数へのアクセスは非常に遅い
クラス内で、for文のなかでメンバ変数をアクセスすることがあります。ひどい場合になると、メンバ変数をループ変数に使ったりします。これは悪いプログラムの典型です。なぜなら、メンバ変数は、その過程において外部から不用意に書き換えられる可能性があるので、通常、変数割付等の最適化は避けられる傾向にあるからです。(VCでは、それが特に顕著です)
ということは、ループ変数にメンバ変数を使うと、恐ろしく遅いプログラムになります。一度ローカル変数に渡す場合とでは、5倍ぐらい変わります。同じく、ループエンドの比較もメンバ変数と比較してしまうと、それは定数とは考えられないので、その部分が最適化されません。速度が必要な部分では、for文の前でローカル変数に渡してやるのは、もはや常識と言えます。
'01/03/15
Tips 9. ノーコストで2つの配列を合成する
ある配列int x[100]と、ある配列int y[100]をくっつけて、int xy[200]のようなものを作り、インデックス0〜199に対してアクセスしたいと思うことがあります。単純に考えれば配列のコピーが必要になります。しかし、それは概念的なものだけで、実際にはコピーは必要としないことがあります。
int& GetXY(int n){ if (n>=0 && n<100) { return x[n]; } if (n>=100 && n<200) { return y[n-100];} throw "インデックスおかしーでー"; } |
場合によっては、これでいいかも知れません。
このバリエーションとして、複数の配列を一気に巡回出来るiteratorというようなものも考えられます。典型的なデザインパターンには存在しない(と思う)のですが、断片化されたオブジェクトに対して、シームレスに(or ひとつのインデクサで)アクセスできるので、非常に有用です。
Tips 10. switch 〜 caseに非定数ラベルを投入する
case文では、定数しか使えません。2と3ならば、
switch (n){ case 2: case 3: System.Out(szMessage); |
と書かなければなりません。これも数が増えてくると大変です。そういう場合はswitch〜caseは使えないのでしょうか?
使えます。
switch (n){ default : if (2<=n && n<=10) System.Out(szMessage); |
のようにdefault文のなかで判別します。(ちょっとダサいですけど)
逆に、VCでdefaultに来ることが無いというのがわかっているときは、
#if _MSC_VER >= 1200 // VC++6.0upper // 到達しないことをコンパイラに通知することにより、上限・下限チェックを省略させる! default: __assume(0); // This tells the optimizer that the default // cannot be reached. As so it does not have to generate // the extra code to check that 'nNum' has a value // not represented by a case arm. This makes the switch // run faster. #endif // _MSC_VER >= 1200 |
このようにdefaultに__assume(0)を放り込んでやると、範囲チェックが不要になってジャンプテーブルで処理するようなコードが生成されやすくなります。(もちろんそのほうが速いコードになります)
Tips 11. ヘッダの多重includeを避ける
Aというファイルが、BとCというファイルをincludeしていて、そのBとCがそれぞれDというヘッダファイルをincludeしていると、AのコンパイルにおいてDは二回includeされることになり、define等が二重に行なわれてしまいます。(コンパイルエラーになります)
これを避けるには、
#ifndef __MY_FILE_NAME_H__ #define __MY_FILE_NAME_H__ /*これはヘッダファイル*/ #endif // __MY_FILE_NAME_H__ |
のようにヘッダファイルの先頭と終了でifndef〜define〜endifを行ないます。
VC++では、
#pragma once |
で済みます。さらに、VC++ならば上のような多重includeを防止するコードを一発で書くためのキーマクロも用意されています。
オンラインヘルプの説明によると、こうです。
キー操作にマクロを割り当てるには
|
そしてマクロ、OneTimeIncludeが、このためのマクロです。
Tips 12. 可変長配列の配列
文字列は、そこへのポインタを値として持ちます。すなわち、
LPCSTR g_alp [ ] = { "ABCDE","CDEFG","EFFGGHH" };
は可能です。同様に、リスト(≒無名配列)もそこへのポインタを値として持てば、
int* x[ ] = { { 1,2,3} , { 2,3,4,5,6} };
のように出来て便利なのですが、これは出来ません。
こういうことがしたいことは頻繁にあります。
ひとつの解決策としては、
vector<vector<int> > x;
とすることでしょうか。これならば、事実上、可変長配列の配列が使えることになります。
ところが、これの初期化はやはり面倒くさい!
const int x[ ] = { 1,2,3,-1,2,3,4,5,6,-1,-1}; // -1がデリミタ
というように-1のような使わない数字を終端記号(デリミタ)として決め、こいつからvectorへコピーしてやるプログラムを書くとか、ファイルからvector<vector<int> >に読み込むクラスを作るかとか、そういう方法で解決することになります。
Tips 13. 初期化子
int x[ ] = { 1,2,3, }; などと、最後の要素のあとにカンマ(,)はつけてもエラーにはなりません。これはこのように
int x[ ] = { 1, 2, 3, }; |
要素ごとに改行を入れて表記しているときならば、最後の要素をどこかと入れ替えたり、付け足したり、コピペ(コピー&ペースト)したりするときに修正せずに済むので便利です。
Tips 14. defineで定数を宣言しない
defineは、プリプロセッサで処理されるので、シンボルデバッガで追っても数字は表示されません。定数は、constキーワードで宣言するように心がけます。
const int x = 5; |
クラス内で使う変数についても、class定義内でstatic constで定数宣言を出来るのですがVC++ではサポートされておらず、クラス宣言内で上のように書いても「純粋仮想関数の宣言に構文上の誤りがあります、'=0' でなければなりません」とかトンチンカンなことを言われるのがオチです。
そこで、.cppファイルのほうで書いて、この変数をファイルスコープに閉じ込めてしまうか、どうしてもクラス内に書きたければ、上の= 5の部分をやめて、static const int m_x;と書き、.cppファイルのほうで、int CMyClass::m_x = 5;と初期化するかです。
ただ、これだと配列の添え字をこれで初期化できなくなるので、enumハックという手口を使うのが定石らしいです。(『Effective C++』:第1項) つまり、
class CMyClass { enum { NUM = 5; } int an[NUM]; }; |
のようにします。
このNUMに外部からアクセスするには、CMyClass::NUMとやります。単にNUMではアクセスできません。またprivateで宣言されていれば外部からもアクセスできません。そこでクラス外からアクセスされたくない定数はprivateなenumにします。
Tips 15. マジックナンバーには名前をつける
マジックナンバーとは、定数、配列サイズ、文字位置、変換ファクタその他、プログラム中に登場するリテラルな数値を指す(『プログラミング作法』 Brian W.Kernighan著) 要するに0と1以外には、たいていマジックナンバーだということになります。
その本のなかで、宣言が可視の配列(=ポインタでない配列)について、配列の要素数をカウントするマクロが紹介されています。
#define NELEMS(array) (sizeof(array) / sizeof(array[0]))
double dbuf[100];
for ( i = 0 ; i<NELEMS(dbuf) ; i++)
...
これを使えば、マジックナンバーはバッファ宣言時に一度登場するだけで済みます。
Tips 16. gotoを自由に使おう!
私は、gotoはきがね無く使います。whileの多重ループから抜け出るのに、goto以外に良い構文が無いからです。
また、ループでなければ、永久ループ for(;;) { } や、while(1) { }を作り、抜けるためにbreakを置く手もありますが、それもあまり綺麗なものではありません。
C/C++には制御構文が欠けています。上の永久ループにしてもそうですし、switch〜caseのcase節に条件式を書けるものも無いです。排他的選択構文、多重にbreakする、break n; 欲しいものを挙げればきりがありませんが、そういうのが無いのだから、無いと割り切ればいいんです。
無理にgotoを使うのを避けてコーディングしようとしても、それは生産性を下げるだけです。
たとえば、(大多数の方に反対されるのは承知の上ですが)
if (x!=1 && x!=2 && x!=5)
{ // xが1,2,5以外のときだけ実行する
...
}
こういうコードにgotoを使ってはいけないのか?と思います。つまり、
if (x==1 || x==2 || x==5) goto Exit;
...
Exit:;
とやってはいけないのでしょうか? 一見、無駄なgotoにも見えますが、この間の処理がある程度の行数がある場合、こちらのほうが見通しがいいですし、しかも、前者とは違い、その部分に対して字下げする必要が無いわけです。また、条件部分もこの場合、後者のほうが読みやすいです。そういう意味では、案外見やすいコードだと思っています。
'01/03/17
Tips 17. caseにはbreak
caseにはbreakを忘れがちです。やねうらおには、学習能力は皆無なのか、いまだにbreakを忘れます。忘れないように、switchと書いたらまず「case : break; 」をコピペしたりします。
ところが、以下のように宣言した変数のスコープが問題なのです。
switch(n) { case 10: int i = 5; break; case 11: int i = 6; break; |
驚くべきことに、2回目のiは、再宣言されているとエラーが出ます。こういうのを見ると頭を掻きむしりたくなります。
もちろん、Tips 3.にあるように、{ } で囲います。ついでに言えばこのとき、breakの前に } を持ってくるか、後に持ってくるか迷うんですが…。
Tips 18. cppとhを切り替える
らうーる君とチャットしてるときに、こんなのもらいました。
Raoul 2001/03/ 2:44 やねさん、こーゆーマクロつかってます?hとcppを切り かえるんですが結構便利〜 Sub OpenCppH() sn = ActiveDocument.FullName '現在編集中のファイル名 cl = len(sn) '現在のファイル名の文字数 '現在が H なら Cpp を表示 if right(sn, 2) = ".h" then s2 = left(sn, cl - 2) + ".cpp" 'Cpp ファイル名 Documents.Open s2, "Text" '開く end if '現在が Cpp なら H を表示 if right(sn, 4) = ".cpp" then s2 = left(sn, cl - 4) + ".h" 'H ファイル名 Documents.Open s2, "Text" '開く end if End Sub Raoul 2001/03/ 2:45 VC++用のマクロなんです。ショートカットキー一つで アクティブな編集ウィンドウをcppとhを切り替えられます。 yaneurao 2001/03/ 2:46 ほほ〜便利やねー。らうーる君のオリジナル?? Raoul 2001/03/ 2:46 いや、webで拾ってきました。どこで拾ったかは覚えて ません:p yaneurao 2001/03/ 2:46 にゃるほど。使わせてもらおっと。 Raoul 2001/03/ 2:47 ツール→マクロ で新規にマクロを作って ツール→カスタマイズ→キーボードでショートカットキーを割り当てる感じで〜 yaneurao 2001/03/ 2:48 さんきゅー Raoul 2001/03/ 2:48 (⌒∇⌒)ノ |
便利なので使わせてもらっていますが、出所不明^^; ちょっと検索してみると…あった! 出所は、これですな。
作者の人、ありがとうございます。とても重宝してます。
VCのオンラインマニュアル見てたら、マクロは他にもいろいろ出来るみたいなので、活用してみると面白そうですね。
Tips 19. わかり辛い表現は避ける
かつて、if (x == 5) と書かなくてはいけないところを if (x = 5) と書いて、このバグの発見がなかなかできなかったプログラマに先輩プログラマが if ( 5 == x) と定数を左辺に書けば、いいよと教えたこともあります。(もし if (5 = x) ならば、この代入は出来ないのでコンパイルエラーになる)
しかし、この手の表現は非常にわかり辛いのです。if ( x = 5 ) と書けばwarning(これをワーニングと間違って読んでいるプログラマをよく見かけますが、ウォーニングが正しい。というか、大学教授を含めてウォーニングと正しく読んでいるプログラマを見たことが無いのですが^^;なんでだろ…)が出るんだから、こう書いておいても何の問題もありません。
また、if ( ! x ) という表現も、どうかと思います。否定演算子はわかり辛いのです。そもそも、ifはboolean(論理型)をとるべきところで、そこに整数を持ってくるC言語の感性自体、どこかおかしいのに、そこにまだ否定演算子(論理否定ではない)を投入するのはどうなのかなーと思います。こういうのは、if ( x == 0) と素直に書いて何が悪いのでしょうか?ほとんどのコンパイラでは、最適化されて同じ実行コードになります。同じ理由で if ( x ) というのも、if ( x!= 0)と書いてもいいと思っています。
ついでに、C++ではNULLは0になりました。しかし、これも if ( p ) という表現はどうなのかな、と思います。素直に if ( p != NULL) と書けばよさそうなものです。そもそも、整数をbooleanに変換するのですらおかしいと思えるのに、ポインタをbooleanに変換しているわけですから、やはり正常な感性とは言いがたい気がします。
Tips 20. 範囲内であるかチェックする表現
Pascal系の言語で、In演算子というのがあって、整数や集合(set)が、その範囲内(集合内)にあるかを調べる機構を持つものがあります。C言語でこれをやろうと思うと、
if ( x >= 0 && x <= 5)
とか書くことになるのですが、初心者のうちは間違えて
if ( 0 <= x <= 5)
と書いてしまいがちです。(もちろん、これは期待している意味にはなりません) これは、数学的な表現 0≦x≦5 を模倣しようとしたのだと思いますが、前者よりは後者のほうが直感的に理解しやすいのです。そこで、前者も、
if ( 0<=x && x <= 5)
と書いてやれば、xがある範囲内であるかをチェックしてますよ、というのが明確になります。Tip19.で定数を左辺に持ってこないほうがいいと書きましたが、これは唯一の例外と言えそうです。
Tips 21. ビット演算子に気をつける
やねうらおのようなセコバッタなプログラマは、int型の各ビットにフラグとしていろんな意味をもたせようとすることがあります。変数を新たに宣言するのが面倒だからというのもありますし、パラメータとして渡すときにint型の変数ひとつで済むほうが複数より使い勝手が良いからです。
ところが、if ( x & 3 == 0)などと書くと期待した通りには動いてくれません。というのも & 演算子のほうが == よりも優先順位が低いからです。この式は、if ( x & ( 3 == 0)) と解釈され、3 == 0は、偽なので0となり、if ( x & 0 )の意味で、0とandするわけでif ( 0 )すなわち、このifは決して成立することは無いのです。
ifが期待した通りに実行されていないとしたら、このようなことをしていないか疑ってみると良いです。
Tips 22. シフト演算子で下位ビットをクリアして良いか?
以前、私のもとで働いていたプログラマが
x = (y >> 3) << 6;
というコードを書いたことがあります。つまり、下位3ビットをクリアして、3ビット左シフトしたかったようです。
なぜ、
x = ( y & ~7) << 3;
と書かないのかという疑問もありますが、この ~ (補数演算子)を知らなかったのかも知れません。
可読性の良さは、おそらくは前者に軍配があがりそうですが、シフト命令はペアリング不可というPentiumの事情を考慮すると後者のほうが良いコードのような気もします。
もちろん、
x = (y >> 3) << 3;
ならば、まよわず
x = y & ~7;
と書いて良いと思います。ただ、この7が2の3乗-1で、ここの部分がマジックナンバーくさいと思うのならば、
x = y & ~((1<<3) - 1);
と書いても構いません。定数畳み込みの最適化によって、y & ~7と同じコードが生成されるはずです。ただ、可読性についてはちょっと疑問もありますが…。
Tips 23. if式には必ず { } を使う
dangling else(ぶら下がりelse)は、曖昧構文の例として有名ですが、よほど明確でない限りif 式には無条件で{ } を使えば良いと思うのです。
仮に、
for (int i = 0 ; i<100 ; i++ ) m_pvClass[i]->reset( ); |
というぐらい明確なコードでも、 { } で囲ってもいいんではないかと思うのです。(私は囲っていませんが^^;)
仮に、うっかり次のように初期化処理を追加すると、このコードは正しく動きません。
for (int i = 0 ; i<100 ; i++ ) m_pvClass[i]->Init( ); m_pvClass[i]->reset( ); |
resetのところは、ループ外で実行されます。しかも、その時のiの値は、100。おそらく、これは配列の範囲外。resetされない上に、範囲外へのアクセスという2重に悲惨な結果があなたを待ちうけています。
'01/03/24
Tips 24. 排他的選択構文
C/C++とその直系たる言語には、排他的選択構文がありません。なぜか、ygs2kではサポートしてます。^^;
排他的選択構文というのは、条件Aをテストして、成立すればA’を実行、成立しなければ条件Bをテストして、成立すればB’実行、条件Bが成立しなければ条件Cをテストして…というように、成立時に実行されるのは、A’,B’,C’…のうちどれか一つしか実行されないような条件構文です。
これは、なくとも構いません。if文で代用できるからです。
if ( A ) A' else if ( B ) B' else if ( C ) C' else D' |
というような構文になります。これが嫌なところは、A,B,C,Dのインデント位置が狂うので、読みにくいことです。ひとつの解決策としては少々強引ですが、else if をifだとかdefineしてしまうことです。(yaneSDK2ndでこのようなマクロを用意しているのは、このためです)
そうすれば、上の例は、
if ( A ) A' ef ( B ) B' ef ( C ) C' else D' |
と、なります。確か、このような省略を許している言語があったように思います。Perlのelsifもそうですが、こういう専用のキーワードを導入すれば、見かけ上はdangling else(ぶら下がりelse)が無くなる(?)ので、良いのかも知れません。
Tips 25. コメント /* */がネスト不可なのは何故か?《サイダーさん》
Tip2.と関連しますが、/* */のネストは一般的に不可です。VCもディフォルトで不可です。
コメントをネストしたいときは#ifdef 〜 #endif
を使います。
しかし、ネスト不可なのを逆用する裏技があります。Tip
1の例を、こう書くのです。
/* MakeInstance( ); TestApplication( ); /* */ |
これならば、コメントアウトするのに、一文字/を加えるだけでOKです。おまけに、途中を任意にコメントアウトできます。たとえば、
//* MakeInstance( ); /* TestApplication( ); /* */ |
とすれば、TestApplication( );だけコメントアウトできます。
そう考えると、/* */のネストは不可のほうが使いやすいような気もします。
Tips 26. 初期化構文について
たとえば、int型のメンバ変数を用意してあったとして―――その初期値は不定です。ローカル変数についても同様です。では、構造体に対して次のように初期化した場合は?
RECT rc = { 100 };
これ、要素が足りないんです(笑) でもコンパイルは通ります。そうです。足りない要素は0として初期化されることが保証されています。
DirectX関連の構造体で、バージョンを確認するために先頭に構造体サイズを突っ込んであるものがたくさんありますが、あれにしても、
DDSURFACEDESC ddsd = { sizeof ( DDSURFACEDESC) } ;
とすれば済みます。(というか、こう書けるようにするために構造体サイズを先頭に持ってきてあるんでしょうけれど…)
Tips 27. ダウンカウントループについて 《ちょぴさん》
アセンブラでなら、カウンタはたいていダウンカウンタにしますね。ダウンカウンタっていうのは、1ずつデクリメントして行って、ゼロになったらループから脱出するだとか、マイナスになったらbreakするというタイプのカウンタです。ゼロになったかどうかは、ゼロフラグが立つから、コンペアするのが楽なんです。
なら、C言語でも、
int i = 100;
while (--i >= 0) {
}
なんてやって100回まわるのはどうでしょうか?確かに、こちらのほうが良いコードが生成されやすいです。
この問題については『プログラミング作法』(Brian W.Kernighan著)のpp.30-31にもありますが、慣用句的な書き方によって、一貫性を保ったほうが良いというのがその本のなかの結論のようです。
要するに、for (int i=0;i<100;i++) ..
というような、書き方にしたほうが一般的だし、Cのネイティブスピーカー(?)ならば一瞬で理解できるコードだと。
それとは別に、ダウンカウンタに変更する最適化は、有名なもので、確かA.V.Ahoの『Compilers』にも書いてあったと思いますが、要するに、
for (int i =0;i<100;i++){
x [ i ] = i;
}
というコードならば、最適化によってダウンカウンタになるかも知れないし、こんなことはコンパイラに任せればいい問題だということです。しかし、
for (int i =0;i<100;i++){
f ( i ) = i;
}
というように、関数呼び出しになっている場合、関数をインライン展開しない限りはこの部分がダウンカウンタに最適化されることは無いでしょう。
私も個人的には、Kernighanと同じ意見で、可読性を損なってまでダウンカウンタに変更することは無いような気もします。
とは言うものの、セコバッタなやねうらおは次のようなコードを書くことはあります。
for (int i =256;i>0;i-=4){
x [ i-1 ] = 3;
x [ i-2 ] = 2;
x [ i-3 ] = 1;
x [ i-4 ] = 0;
}
ループを2^n回、アンロール(展開)することによって、高速化を計ろうというものです。x[ r + d ]というアドレッシングが、アセンブラに落ちたときに1命令になることを利用しています。
Tips 28. CかC++か判別する
はっきり言って、何の役にも立たないのですが、CかC++かを判別するコードというのがあります。たとえば、こうです。
int x = 1 //* */ 2 ; |
Cならば、x == 0, C++ならばx == 1になります。C++ならば、//以下がコメントとして認識され、x = 1;の意味になりますが、Cならば、//は認識されずにx = 1 / 2;⇒0になります。(Cとは言っても//をサポートしていない場合に限りますが)
同様に、これを応用して、/* */がネストを許すかを判定するために、次のようなコードを書くことは出来ます。これには、いろいろバージョンがありますが、一応、私のオリジナルのやつを紹介しておきます。( /* */のネスト中は // を無視する仕様だと仮定しています)
int x = /* /* */ 0 // */ 1 ; |
これまた、役に立つかどうかは不明。バリエーションとしては、#ifdefがネスト可能かどうか調べるだとかもあります。ほとんど何の役にも立たない(#ifdefのネストをサポートしていない処理系でコンパイルしたときに、warningを出すようにするだとかその程度)ので、紹介しません^^;
Tips 29. signedな型に対して>>しない
intやcharがsignedであるかunsignedであるかは実装系依存だったような覚えがあります。とは言っても、普通はsignedでしょう。(その昔、X68K用のXCがunsignedだったような覚えが無くもないですが^^;) charがsignedになっているので、値比較するときに0x80〜はマイナス扱いされて半泣きになった経験は誰しもがあるかも知れません。
だから、いま、intはsignedとします。
int x = -1; x >>= 1; |
さて、xの値は?
実は、>>が、算術シフトか、論理シフトかは規定されていません。VCでは、結果は、-1となります。つまり、算術シフトなのです。よって、
int x = 0xffffffff; x >>= 1; |
ならば、x==0xffffffff;のままなのです。ビットシフトして上位バイトに0が埋まることを期待していたのにそうなっていないのです。えらいこっちゃ〜ですね(笑)
そんなわけで、ビットシフト(論理シフト)を期待するときは、signedではなくunsignedで行なうのが普通です。
intに対して、>>でビットシフトしてたら、本当に正しいのか疑ってみましょう。普通はこの手のはintではなく、DWORDで書くのはそういう理由なのです。
VCの製作者が、>>を算術シフトとして実装したのは、signed⇒算術シフト , unsigned⇒論理シフト という使いわけをするためかも知れませんね。
Tips 30. ループ変数のスコープを限定するマクロ《NEXさん》
Tip.3関連ですが、forの初期化部分で宣言したループ変数のスコープをfor内部に限定するには、
#define for if(0); else for |
このマクロで可能のようです。何とトリッキーな!
単純には
#define for if(1) for |
でもいいんですが、これだとforループのあとに不要なelseがあってもコンパイルエラーにならないので前者のほうがいいです。
Tips 31. fscanfのバッファサイズを制限する
fscanfで%sで文字列を入力すると、バッファサイズをあふれかねません。バッファサイズは、%32sのように数字を入れて指定できますが、マジックナンバーを多用することになるので、普通は
char buf[256]; char tmp[16]; wsprintf(tmp,"%%%ds",sizeof(buf)-1); fscanf(fp,tmp,buf); |
とかやります。
それはともかく、いまどきfscanfなんか使わんでしょーに!とかそういう気もしますが(笑)
Tips 32. zero originにする
配列の添え字は0から始まります。1から始まるほうが不自然なのです。たとえば、8個ずつ書こうと思っていた、このTipsも、Tip.1から始めてしまったばっかりに、n回目のTipsの開始番号は、8×n - 7という不自然な数字になってしまっています。おかげで、前回間違えてTip23を書いたところで終了させてしまいました(笑)
もし、0から始めていれば、n回目のTipsは、8×(n-1)から始まることになり、かつこの、n回目のnというのも0から始めれば n 回目のTipsは 8 × nというようになります。
この結果からもわかるように、番号指定するところは、なるべく0で始めたほうが得策です。このように、0から始まっていることをゼロオリジンと呼びます。
ところで、pascal系の言語では、ゼロオリジンでない配列をサポートしているものもあります。
array of integer [ 5..10]; |
これは、これで使えると思います。こういう記法、ちょっと魅力だったりして(笑)
'01/04/23
Tips 33. ビッグエンディアンかリトルエンディアンか判別するコード 《ロベールさん》
int x = 1; x = *(char*)&x; // 0:ビッグエンディアン/1:リトルエンディアン |
にゃるほど。にゃるほど。
Tips 34. 分岐最適化
最近のコンパイラは利口なので、下手なプログラマがアセンブラで書くより、よっぽど優秀なコードを吐くというのは通説でしょう。
たとえば、ループ内で不変な式や定数をループ外に追いやったり、switch〜caseは数に応じてジャンプテーブルを生成したりします。
しかし!
絶対に現状のコンパイラでは人間より最適化できないと言う種類のものがあります。
それは、分岐条件の優先順位です。
具体的に言いましょう。排他条件A,B,Cがあるとします。すなわち、A,B,Cはそれぞれ同時に満たすことは無いとします。これを、
if ( A ) { do_A; } else if (B) { do_B; } else if (C) { do_C; } else { do_nothing; }
というようなコードで、比較〜条件成立時の処理を書いたとします。いま、AよりB、BよりはCのほうが発生確率が高いとします。だとすれば、これは
if ( C ) { do_C; } else if (B) { do_B; } else if (A) { do_A; } else { do_nothing; }
と、C,B,Aの順番で条件をチェックしたほうが速いコードになります。
アセンブラで、この種の生起確率に基づく条件分岐の優先順位づけしてのコーディングというのは、日常的にされていると思うのですが、C言語でこれを明確に意識している人は少ないように思います。特に、AやBの条件テストに結構、計算コストが必要な場合はなおさらです。
とは言っても、ソースの可読性が落ちるので、あまりぐちゃぐちゃにするのもどうかと思うのですが..。
Tips 35. コンピュータは2進数で計算している
普段、何気に使っている浮動小数ですが、内部ではまず間違いなく2進数で扱われています。当然、掛け算によって微妙な誤差を生じます。
1.0 / 3 * 3 == 1.0 にならない(内部表現で一致しない)のは言うに及ばず、たとえば、文字列⇒数値への変換するときに、×10を繰り返すアルゴリズムだと、そこで誤差が発生します。実際はほとんど問題にならないですが、最終的にfloat型で結果を得たい場合、精度が問われるなら途中結果はdouble型で計算しておくほうが望ましいのかも知れません。(float型なんて無くせばいいと思っているのは、やねうらおだけでは無いような..)
たとえば、0.1を2進数で表現するとき、普通これは循環小数として表現されています。そこで、0.1×10 == 1.0にはなりません。
Tips 36. else hack 《悼さん》
掲示板で質問をいただいたのですが、
auto_vector_ptr<ClassA> avpA; というのがあって、これに入っているClassA 全てに、ある処理をしたいとします。 で、こうやっていたのですが、 for(i=0;i<avpA.size();i++) if (avpA[i]!=NULL) {(処理)} //1 こう書くと、vA[0]!=NULL のとき、一度処理してi==0 のまま(i++ されず) ループから突き抜けてしまいます。 (なんでこういう変な書き方をしてるかというと、『for(i=0;i<vA.size();i++) if (vA[i]!=NULL)』の 部分をマクロにしたいから) もちろん、 for(i=0;i<avpA.size();i++){ if (avpA[i]!=NULL) {(処理)} } //2 for(i=0;i<avpA.size();i++){ if (avpA[i]==NULL) continue;(処理)} と書けば意図どおりに動くのですが(通った後はi==vA.size())、 for(i=0;i<avpA.size();i++) if (avpA[i]!=NULL) {(処理)continue;} //3 こうすると2 と同じく上手くいきます。 しばらく理解できずに混乱してたのですが、これってこういうもんなんでしょうか? 3 みたいにcontinue 書かななりませんというのも面倒ですし、1 の書き方ができると楽なんですが…。 でもやっぱり2 みたいに書くしかないんでしょうかね。 |
これC++の構文規則がなんとなくおかしい気もしますね(笑)
一応、解決策としてはTips.30の手法を応用できます。つまり、2.の方法の最後の 閉じ括弧( } )を省略するための技法として、Tips.30で用いたelse hack(と勝手に命名)が使えます。
for(i=0;i<avpA.size();i++) if (avpA[i]==NULL) ; else (処理) |
このように変形します。そうすれば最後の閉じ括弧が省略できるので、for文からelseまでをdefineでマクロとして登録してしまえば、Rubyのイテレータ抽象(?)みたいなことが出来ますね。
Tips 37. DirectXの全ヘルプをMSDNに統合
これ、やりたいなー、やりたいなーと思って、インターネットを検索してたら、こんなページがありました。
http://www.sofarts.com/computer/env-soft/windows/app_programming/vs/index.html
他にも、リモートデバッグの方法、Visual Studioで秀丸エディタを使う方法などが紹介されており、とても参考になるページです。
↑のリンク先が変更になりしまた。↓↓('03/04/27)
http://www.sofarts.com/oldnew/computer/env-soft/windows/app_programming/vs/vs-dx-msdn.htm
Tips 38. 秀丸で.hと.cppを切り替える
Tips 18の秀丸版を、鈴森れいやさん
http://www02.so-net.ne.jp/~leiya/
が作られたそうで、ご紹介しておきます。(このリンク先の、『思考の淵』のところからDLできます)
'01/09/21
Tips 39. enumのサイズを得る 《Tick Tocさん》
enumされた要素数を知ることは、出来ません。
enum XYZ { AAA,BBB,CCC};
に対して、sizeof(XYZ)==sizeof(int)です。3は返りません。
そこで、enumには、最後に番人を置くと良いです。
enum XYZ { AAA,BBB,CCC,sizeof_XYZ};
こうしておけば、
int m_an[sizeof_XYZ];
というようにenumの要素数を持った配列を用意できます。
C++になってから、配列は、STL::vectorを使うことが多いので、あまり必要性を感じないですが、知っていると便利なこともあります。
Tips 40. std::stringに対する考察
別にTipsでも何でも無いのですが(笑)、よくこういうミスを犯しませんかーという例。
std::stringで、その文字列へのポインタが欲しいときは、c_str( )という関数で取得します。
string s; s = "ABC"; printf(s.c_str( )); |
言うまでも無いことですね。あと、あまり使っている人が居ないようですけど、stringにはoperator [ ]が定義されています。
string s; s = "ABC"; printf("%c",s[1]); // Bが表示される |
のように使えます。まあ、そこは、c_str( )[ 1] と書いてもいいのですが。問題は、この返し値がcharだということです。
また、stringには、operator = (char )が定義されていますので、
string s; s = "ABC"; string t; t = s[1]; // t == "B" t += s[2]; // t == "BC" |
と書くことも出来ます。ところが、
string s; s = "ABC"; string t; t = s[1]+s[2]; // t == 'B'+'C' |
このようなプログラムですと、'B'のアスキーコードと'C'のアスキーコードがcharの範囲で加算されて、その値(char)に対応する文字がtに入ります。当然、これは望むべきものとは違います。これ、暗黙の変換に騙される典型的な例です。もう一つ、ハマリそうなのが、
string s; s = "ABC"; string t; t = s[2] + "DEF"; // これは何が返るか? |
こういうパターンです。"DEF"の型は、char*(厳密にはconst属性があるが)。s[2]はcharですから、これはポインタ演算を意味します。つまり、"DEF"のアドレスに、s[2]の値を加算した、char*が、右辺値となり、それがstringに暗黙の変換がなされて代入されます。当然、この場合、不正なポインタということになります。
暗黙の変換には気をつけようなどと言いたいわけではなく、なぜこうなってしまうのか、そのへんを考えていただきたいのです。その最大の原因は、C/C++において、charが、character(文字)を意味するのでは無く、単に1バイト整数に過ぎず、数値として加算が出来てしまうあたりに問題があります。
そんなことを言い出すと、文字列は本当にchar*で表して良いのか、という問題もあります。
stringの定義は、
typedef basic_string<char> string; |
です。これが何を意味するかおわかりでしょう。これならば、UNICODEのような2バイト文字に対応させるには、
typedef basic_string<WORD> unicode_string; |
とでもすれば良いように見えます。実際、UNICODEの場合は、そうでしょう。
ところが、SHIFT_JISのように、1バイトと2バイト文字が混在しているような文字セットの場合、こういう設計ではうまくいきません。
※ その場合、どうすれば良いかは、いずれ機会を改めて書きます。
Tips 41. メンバ関数に対するインテリセンス
VC++でインテリセンスというのがあります。これ、確かに結構便利なことは否定はしませんが、あまりインテリでない気はします。(笑)
たとえば、メンバ変数/関数の一覧を表示して、そこから選びたいような場合はどうすれば良いのでしょうか?長い関数名だと忘れてしまうことだってあります。
インテリセンスは、「.」や「->」と書いたときに、その左辺値を見て自動起動(?)するようです。ならば、
this-> |
とタイプしてやるというのはどうでしょうか? これで、メンバ変数/関数の一覧が出てきます。結構、便利なのです。
Tips 42. namespaceを活用する 《かーくん》
メールでの投稿から引用します:
天才プログラマー養成ギブス「第六章.未来形でプログラミングする 2000/10/13」の最後のほうに書いてあった、
| そもそも、最後に派生されたクラスを使う構文さえあれば、
| こういう必要は無くなります。あるいは、クラス宣言時に、
| そのクラスと摩り替える特殊な派生方式があれば良いのです。
ということについて、気になったことがあるのですが…。
yaneGraphicsLoader.hをyaneGraphicsLoader.default.hにリネームして、
新しいyaneGraphicsLoader.hの内容をこんな感じにするとか…
// yaneGraphicsLoader.h namespace DefaultGraphicsLoader { #include "yaneGraphicsLoader.default.h" }; class CGraphicsLoader : public DefaultGraphicsLoader::CGraphicsLoader { // CGraphicLoader修正のための擬似派生クラス }; |
これだと、Factory再登録の手間も省けて、必要に応じて修正前のクラスのメソッドも呼べます。
もしこれが修正ではなくて、あるアプリケーション固有の拡張ならば、ヘッダファイルをリネームする代わりにyaneGraphicsLoaderEx.hを作って、yaneGraphicsLoader.hをnamespaceで包んで#includeしてやります。
以上、かーくんのメールからの引用です。なるほど、namespaceで囲って、そのなかでincludeするというのは、なかなか斬新な発想だと思います。
《捕捉 '01/09/22》
当然、もとの.cppファイルのほうも、namespaceで囲まないといけないのですが、元の.cppファイルをいじったのでは何にもならないので、元の.cppファイルは、yaneGraphicsLoader.default.cppとでもリネームして、こいつは、プロジェクトには含めず、
新しく作ったyaneGraphicsLoader.cppにて、
namespace DefaultGraphicsLoader {
#include "yaneGraphicsLoader.default.cpp"
}
とやると良いです。
Tips 43. namespaceのネスティング
よく言われるのですが、いくらnamespaceで名前空間を作っても、他人のライブラリとかとくっつけたときにその名前空間自体がぶつかってしまうことがある、というものです。
確率的には低いですけど、原理的にはそういうことも有ると思います。
一応、namespaceは、ネスティングできますので、
namespace abc { namespace def { int x; } } |
Tips 42.の応用して、
namespace hoge_lib { #include "hoge_library.h" } |
というように、他のライブラリのヘッダをまるごとnamespaceによって何かの名前空間に閉じ込めてしまえば、何とかなることもあります。
Tips 44. クラスのコンストラクタ・デストラクタ 《UeSyu》
UeSyuさんの掲示板への投稿から:
ちょっと思いついたのですが、「クラスのコンストラクタ・デストラクタ」というのがあれば結構便利だと思うのですが、どうでしょうか?
C++で言えば、スタティックメンバの初期化や解放を行うための静的メンバ関数、といった所です。C++には無いですよね?たしか。
なるほど。C++には、そういうのはありません。私は、その書き込みのレスとして、
Res:やねうらお(185) 題名:うむむ 投稿日
: 2001年8月10日<金>19時59分 staticなメンバ変数の使いみちなのですが、C++では初期化順序が不定のため、依存関係が明確なケース以外使えません。私は、staticなメンバ変数は使ってはならないものだと思っています。yaneSDK2ndでは若干手抜きコーディングのためstaticなメンバ変数を使ってますが、あれも完全に無くすことが出来ます。 クラスのコンストラクタ・デストラクタも、その生成/解体順序が不定になると、便利そうではありますが、実のところ使いものにならないと思います。 staticなメンバが必要になるケースというのは、Singletonや、参照カウントの計測などが相場ですが、どちらも実はstaticなメンバなど必要ありません。要は、クラス設計次第だと思います |
と書いてしまったのですが、クラスのコンストラクタ、デストラクタが使えると、便利そうではあります。
一応、こうやれば良いです。
class CHoge { public: class ClassInitializer { ClassInitializer( ){ CHoge::Class_Constructor( ); } ~ClassInitializer( ){ CHoge::Class_Destructor( ); } }; static ClassInitializer m_Initializer; static void Class_Constructor( ){ // クラスの初期化 } static void Class_Destructor( ){ // クラスの解体 } }; |
この場合、理想的には、CHogeクラスのインスタンスを生成していないときは、コードの最適化によって、このクラスの初期化と解体も、削除されるべきなのですが、それをC++コンパイラに望むのは酷というものかも知れません。そう考えると、言語レベルでサポートされるべき機能のような気がします。たとえば、Javaにはstatic初期化ブロック(上のクラスのコンストラクタ)というのが言語レベルでサポートされています。クラスのデストラクタに相当するものは無かったように思いますが。
Tips 45. お手軽コメントアウト
何らかのテストをしているとき、bool型変数に代入しているtrueとfalseを何度も切り替えたいときがあります。こういうとき、
bool b = //* true; //*/ false; |
この一つ目の / を消すだけで、falseになります。テストのときだけ、こちらの関数を呼び出す、というような場合もこのテクを使うことが出来ます。
Tips 46. プログラムの作業料
私、以前から疑問があるのですが、プログラマの作業料や、コードに対する値段というのは、成功報酬か、さもなくば、ステップ数(工程数≒行数)であることが多いのですが、どうして、ソースのバイト数にしないのでしょうか?ゲーム業界で、シナリオの相場というのは、「1K500円〜1,500円」などと言われていますが、どうして、ソース量も、このようにバイト数で計り売りしないんでしょうか..少なくとも、ステップ数よりはずっといいと思います。
私の過去のプログラムを調べてたら、多いときで1日、30KB〜60KB。このペースで開発し続ければ、月、900KB〜1.8MBになる計算になりますが、さすがに試行錯誤したり、調べものをしたりしていると、こんなペースでは開発できません。通常、ゲームプログラミングならば、それほど未知の技術を投入するわけでも無いので、C++のソースコードとして、200KB〜500KB/月ぐらいでしょう。これからすれば、ゲームプログラムも1K 500円〜1500円ぐらいが相場のような気もします。会社間の取引になると、もう少し高いですが。
Tips 47. EZ-プログラミングの勧め
Aという処理、Bという処理、Cという処理それぞれに似た部分がある場合、関数をひとまとめにしてif文やswitch case、仮想関数の呼び出しを使って書いたりするのが、普通だと思っておられる方が多いでしょう。
しかし、それは間違いだと思うのです。
突貫工事ならば、コピペして違う処理を行なう場所だけ書き換えるほうが断然、早いのです。Windowsのプログラムなんて、プログラムサイズが多少大きくなったところで、どうってことは無いことが多いです。保守しにくくなると言っても、少しコメントを書いておけばわかります。
特に、私の場合、納品を急いでいるときは、「この関数は、××関数のコピペ+α」とだけコメントを書いて、あとで気が向いたらなんとかしよう、という一時コードを書きます。たいてい、そのコードはそのまま納品してしまうことになるんですけど(笑)
その昔、5年ぐらい前に、コンパイル(某ぷよぷよのメーカー)のプログラマの書いたソースを少しいじったことがありますが、その人のソース、そういうコピペがやたら多かったんです。それを見て、ああ、こういうのもアリなんだなー、とか思ったのです。
'01/10/08
Tips 48. インデックスアドレッシングの勧め
DWORD* pSurface = (DWORD*)pDW; DWORD* pSurface2 = (DWORD*)pDW2; for(int j=0;j<480;j++){ for(int i=0;i<640;i++){ *pSurface = *pSurface2; pSurface++; pSurface2++; } } |
640×480の32bppのサーフェースをまるごと転送するプログラムだが、このような大きなメモリの転送は、下手なプログラムを書くと、アプリケーションに恐ろしい速度低下をもたらす。たとえば、上の例では、1回のループごとに、ポインタのインクリメントを2回やって、しかも、ループのエンドチェックを1回やっている。これで速いはずがない。このポインタのインクリメントを削除してやればどうだろうか?
DWORD* pSurface = (DWORD*)pDW; DWORD* pSurface2 = (DWORD*)pDW2; for(int j=0;j<480;j++){ for(int i=0;i<640;i++){ pSurface[i] = *pSurface2[i]; } pSurface+=640; pSurface2+=640; } |
[i]とアクセスすると、一見、余計なオーバーヘッドが発生するかのように見える。しかし、Pentium系のCPUでは、そうでは無い。
mov [eax + edx * 4],ebx
というような、インデックスアドレッシングで処理するコードも、1クロックなのだ。これにより、上の例ならば、インクリメントが消えた分だけ速くなる。前のコードに比べて、2倍ぐらい速いだろう。
Tips 49. アンロールの勧め
Tip48のプログラムで、ループのエンドチェックは、分岐予測が最初の1回を除いて当たり続けるから、分岐予測ミスのよるペナルティは、実はほとんど無視できる。しかし、次のように、さらにアンロールすることによって、20%程度速くなる。
DWORD* pSurface = (DWORD*)pDW; DWORD* pSurface2 = (DWORD*)pDW2; for(int j=0;j<480;j++){ for(int i=0;i<640;i+=8){ pSurface[i] = *pSurface2[i]; pSurface[i+1] = *pSurface2[i+1]; pSurface[i+2] = *pSurface2[i+2]; pSurface[i+3] = *pSurface2[i+3]; pSurface[i+4] = *pSurface2[i+4]; pSurface[i+5] = *pSurface2[i+5]; pSurface[i+6] = *pSurface2[i+6]; pSurface[i+7] = *pSurface2[i+7]; } pSurface+=640; pSurface2+=640; } |
言うまでもなく、Pentium系のCPUでは、
mov [eax + edx * 4 + 28],ebx
と、インデックス付きのアドレッシングでも1クロックだからだ。ただし、このとき、最適化により、ループ変数iがレジスタ割付されていることが前提となる。レジスタ割付されていないと逆に遅くなる。このへん、注意が必要である。
Tips 50. 変数のレジスタ割付
どういうときに、変数はレジスタ割付されるのだろうか?これは、コンパイラ依存だろう。だから、VC++に限定して話をするのだが、VC++では、メンバ変数は、最適化によってレジスタ割付されることは無い。メンバ変数のレジスタ割付をやると、最適化が現実的ではなくなってしまうということなのだろう。VC++ほどの(最適化に力を注いでいる)コンパイラで最適化されないのだから、一般に最適化しないと考えたほうが良いのかも知れない。
これに関しては、SYNさんのページを参考としてあげておきます。誰もが一度は陥る問題だから..。