C++ コーディングの作法
ここではコーディング規約/コーディング標準(coding standards/coding guidelines/coding rules)の話をしましょう
既に明文化され公表されているコーディング規約は幾つもありますし、クライアントごとプロジェクトごとに従うべきコーディング規約というものは決まっていますが、ここではプログラマとして最低限身に付けるべき作法とも言いえるような基本的なことだけ紹介してみましょう
プロジェクトのコーディング規約を優先する
ここに挙げるものは基本的なものではありますが、絶対的なものではありません
ここにあるものよりプロジェクトのコーディング規約を優先してください
また新人さんは必ずプロジェクトのコーディング規約に従ってコードを書くようにしてください
自分のスタイルと合わないところもあるでしょうけれども、勝手に無視をせず自分のスタイルよりコーディング規約を優先させなければいけません
一番やってはいけないのは、決まりを勝手に無視をすることです
どの決まりも理由があって在るものですから、決して勝手に無視をしてはいけません
この決まりは守らなくてもいいんじゃないか、と思った時にはまず先輩や上長に相談してみてください
はじめに
アプリケーションは使い手のために書く。コードは読み手のために書く。という当たり前のことが重要です
プログラムは文芸作品ではないのですから、書き手のスタイルだとかそんなことはどうでもいいのです
その上で動作への影響のある方針として重要なのは「自分よりコンパイラを信じる」「初期化を確実に」ということです
動作への影響のないところで重要なのはただ一点「書き手の意図が読み手に正しく伝わるかどうか」というところです
そこから「未来の自分は他人だと思って書く」「コードの見た目の短さより読み手の読みやすさを重視する」といった方針も出てきます
サイズ・要素数を自分で指定しない
オブジェクトのサイズや配列の要素数を何度も計算したり自分で指定したりしてはいけません
sizeof演算子や elementsofマクロなどを使ってコンパイラにやらせましょう
コンパイラは決して間違えません
×ダメ
int foo[100]; memset( foo, 0, sizeof( int ) * 100 );
○良い
int foo[100]; memset( foo, 0, sizeof( foo ));
サイズとは関係ないけど初期値の代入でなく初期化を使うこと
int foo[100] = {}; //memset( foo, 0, sizeof( foo ));
×ダメ
int foo[100]; for( int n = 0; n < 100; ++n ){ }
○良い
int foo[100]; for( size_t n = 0; n < elementsof( foo ); ++n ){ }
サイズとは関係ないけど初期化すること
int foo[100] = {}; for( size_t n = 0; n < elementsof( foo ); ++n ){ }
オブジェクトの型を自分で指定しない
×ダメ
型を自分で指定してはダメ
foo_t foo[] = { . . . }; for( size_t n = 0; n < sizeof( foo ) / sizeof( foo_t ); ++n ){ }
△elementsofマクロなどを使うようにした方が良い
foo_t foo[] = { . . . }; for( size_t n = 0; n < sizeof( foo ) / sizeof( foo[0] ); ++n ){ }
○良い
foo_t foo[] = { . . . }; for( size_t n = 0; n < elementsof( foo ); ++n ){ }
×ダメ
型を自分で指定してはダメ
foo_t foo; memset( &foo, 0, sizeof( foo_t ));
○良い
foo_t foo; memset( &foo, 0, sizeof( foo ));
○より良い
コンストラクタや初期化を使った方がより良い
foo_t foo = {}; foo_t foo();
同じ式を何度も書かない
同じ式が何度も出てくるならマクロにするなり関数にするなり、あるいは値をローカル変数に置くなりしてその式に適切な名前を付けましょう
×ダメ
dw1 = (wh1<<32)|wl1; dw2 = (wh2<<32)|wl2;
○良い
#define MAKELONG(l,h) ((unsigned long)(((unsigned long)(wh1)<<32)|(unsigned long)(wl1))) dw1 = MAKELONG(wl1,wh1); dw2 = MAKELONG(wl2,wh2);
×ダメ
p = malloc( sizeof( wchar_t ) * ( cch + 1 )); memset( p, 0, sizeof( wchar_t ) * ( cch + 1 ));
○良い
size_t cb = sizeof( wchar_t ) * ( cch + 1 ); p = malloc( cb ); memset( p, 0, cb );
メンバ変数は必ず全部初期化リストで初期化する
クラスにコンストラクタを持たせるなら、必ず全ての基底クラスと全てのメンバ変数をコンストラクタの初期化リストで初期化すること
必ず!全部!初期化リストで!
×ダメ
コンストラクタでの初期値の代入ではダメ
class CFoo { int m_foo; . . . CFoo(){ m_foo = 0; } };
○良い
class CFoo { int m_foo; . . . CFoo(): m_foo(0) { } };
初期値を設定するという意味では同じでも文法上はまったく違うものです
初期化はコンストラクタが処理しますが、代入は代入演算子が処理します
初期化リストは必ず全部宣言順に並べる
初期化リストは必ず全部宣言順に並べること
全部と言ったら全部
×ダメ
class CFoo { int m_foo; int m_bar; . . . CFoo(): m_bar(0), m_foo(0) { } };
○良い
class CFoo { int m_foo; int m_bar; . . . CFoo(): m_foo(0), m_bar(0) { } };
初期化は初期化リストに書いてある順ではなく宣言順に処理されます
初期化リストで初期化しないものは必ずコメントを残す
全部と言ったら全部
だけど、パフォーマンスに影響するとか適切な理由があってどうしても初期化しないものは「必ず」コメントに残すこと
×ダメ
class CFoo { char m_foo_buffer[100]; char* m_foo; . . . CFoo(): m_foo(m_foo_buffer) { } };
○良い
class CFoo { char m_foo_buffer[100]; char* m_foo; . . . CFoo(): //m_foo_buffer(), m_foo(m_foo_buffer) { } };
×ダメ
class CFoo { int m_foo; . . . CFoo(){ m_foo = 0; } };
○良い
本当はこんな意味も無いのに初期化リストで初期化しないのはダメ
ちゃんと理由のあるときは良い
class CFoo { int m_foo; . . . CFoo() //:m_foo(0) { m_foo = 0; } };
基底クラスも必ず初期化リストで明示的に初期化する
初期化リストに書いていないメンバ変数は本当に初期化されませんが、基底クラスは初期化リストに書いてあっても書いていなくても必ず初期化されます
必ず初期化はされますが、そういった動作は関係なく、必ず明示的に初期化リストに書くこと
×ダメ
class CFoo : public CBar { int m_foo; int m_bar; . . . CFoo(): m_foo(0), m_bar(0) { } };
×ダメ
このコメントは嘘だからダメ
コメントにしても基底クラスは必ず初期化される
class CFoo : public CBar { int m_foo; int m_bar; . . . CFoo(): //CBar(), m_foo(0), m_bar(0) { } };
○良い
class CFoo : public CBar { int m_foo; int m_bar; . . . CFoo(): CBar(), m_foo(0), m_bar(0) { } };
POD なメンバを持つ non-POD な型には必ずコンストラクタを用意する
×ダメ
params_t は POD なメンバを持つ non-POD なのにコンストラクタがない。ダメ
struct params_t { std::vector<int> items; int value; };
×ダメ(バグ)
下記のように non-POD な型のインスタンスをデフォルト初期化してもその non-POD な型が持つ POD なメンバは正しく初期化されません
一見したところではゼロ初期化されそうに見えるので大変危険です
struct params_t { std::vector<int> items; // このメンバは non-POD なのでユーザー定義のコンストラクタが無くても常に初期化される int value; // このメンバは POD なのでユーザー定義のコンストラクタで初期化しない限り決して初期化されない! }; class foo_t { int aaa; params_t params; . . . foo_t(): aaa(), // 初期化リストで初期値を指定せずに POD を初期化するとゼロ初期化 params(), // コンストラクタの無い non-POD をデフォルト初期化 . . . { } };
○良い
POD なメンバを持つ non-POD な型には必ずコンストラクタを用意する
struct params_t { std::vector<int> items; int value; params_t(): items(), value(0) { } };
ローカル変数は必ず初期化する
すべてのローカル変数は必ず初期化しましょう
初期値の代入ではダメ
×ダメ
int foo; foo = 0;
○良い
int foo = 0;
×ダメ
int foo;
foo = get_foo();
○良い
int foo = get_foo();
×ダメ
foo_struct foo; memset( &foo, 0, sizeof( foo )); foo.dwSize = sizeof( foo );
○良い
foo_struct foo = {};
foo.dwSize = sizeof( foo );
○良い
foo_struct foo = { sizeof( foo ) };
×ダメ
int n; for( n = 0; n < 100; ++n ){ }
×ダメ
無駄に初期値が違う
int n = -1; for( n = 0; n < 100; ++n ){ }
○良い
for( int n = 0; n < 100; ++n ){ }
そうは言っても後で初期値を代入する配列を無駄に初期化したりするのも無駄だから、そういうのはしょうがないけど、そういうのは初期化の代わりにコメントを書くこと
×ダメ
int foo[FOO_MAX]; for( size_t n = 0; n < elementsof( foo ); ++n ){ foo[n] = ... }
×ダメ
間に余計な処理を入れたらダメ
int foo[FOO_MAX]; // 後で初期値を代入するから初期化はしないよ . . . 余計な処理 . . . for( size_t n = 0; n < elementsof( foo ); ++n ){ foo[n] = ... }
○良い
int foo[FOO_MAX]; // 直後のループで初期値を代入するから初期化はしないよ for( size_t n = 0; n < elementsof( foo ); ++n ){ foo[n] = ... }
×ダメ
必ずどこかのパスで値が設定されるとしても未初期化にしてはダメ
else や default のときの値で初期化すること
int foo; if( bar == 0 ){ foo = 1; } else if( bar == 1 ){ foo = 7; } else if( bar == 2 ){ foo = 13; } else{ foo = 17; }
○良い
(本当は数値リテラルには名前を付けなきゃダメ)
int foo = 17; if( bar == 0 ){ foo = 1; } else if( bar == 1 ){ foo = 7; } else if( bar == 2 ){ foo = 13; }
○良い
int foo; // 後で初期値を代入するから初期化はしないよ if( bar == 0 ){ foo = 1; } else if( bar == 1 ){ foo = 7; } else if( bar == 2 ){ foo = 13; } else{ foo = 17; }
×ダメ
int foo; // 後で初期値を代入するから初期化はしないよ . . . (間に余計な処理) . . . if( bar == 0 ){ foo = 1; } else if( bar == 1 ){ foo = 7; } else if( bar == 2 ){ foo = 13; } else{ foo = 17; }
こういったある数値を別の数値に対応づけるような処理には名前を付けられるはずなので、関数化するとより良いでしょう
ローカル変数を使いまわさない
ローカル変数をケチって関係の無い用途に使いまわさない
いまどき別にローカル変数がいくら増えたところでどうということもないんだからローカル変数は必要なだけ用意すること
×ダメ
char* temp = strrchr( filename, '\\' ); if( temp ){ *temp = '\0'; } else{ temp = filename; } temp = strrchr( temp, '.' ); if( temp ){ . . . }
○良い
char* pFilePart = strrchr( filename, '\\' ); if( pFilePart ){ *pFilePart = '\0'; } else{ pFilePart = filename; } char* pFileExt = strrchr( pFilePart, '.' ); if( pFileExt ){ . . . }
ローカル変数は必要になったところで宣言する
ブロックの先頭でまとめてローカル変数を宣言しない。ローカル変数は必要になったところで宣言すること
特に関係ないブロックには置かないこと
×ダメ
int i; int j; int k; . . . for( i = 0; i < 100; ++i ){ for( j = i+1; j < 100; ++j ){ . . . k = foo(i,j); . . . } }
○良い
本当はループ回数に数値リテラルを使うなんてのはダメ
(さらに内側のループは本当は while にすべき)
for( int i = 0; i < 100; ++i ){ for( int j = i+1; j < 100; ++j ){ . . . int k = foo(i,j); . . . } }
出力パラメータは必ず値を設定する
関数の出力パラメータは必ず値を返すこと
戻り値などの条件によって出力パラメータが設定されたり設定されなかったりということをしてはいけません
×ダメ
*baz が設定されないパスがある
int foo( int bar, int* baz ){ int qux = get_qux( bar ); if( qux ){ if( baz ){ *baz = get_baz( qux ); } } return qux; }
この関数を呼び出す側は baz に触ってもいいかどうかを自分で責任を持つ必要がある
int baz; if( foo( bar, &baz )){ // ここなら baz に触っても安全 } else{ // ここで baz は不定値 }
呼び出す側は baz を確実に初期化しておく
int baz = invalid_baz; foo( bar, &baz ); // baz に触っても安全
そうでなければ危険
int baz; foo( bar, &baz ); // ここで baz が不定値の可能性がある
○良い
呼び出し側はいつ *baz に触っても大丈夫
int foo( int bar, int* baz ){ if( baz ){ *baz = invalid_baz; } int qux = get_qux( bar ); if( qux ){ if( baz ){ *baz = get_baz( qux ); } } return qux; }
コード中に数値リテラルを使わない
このページでは数値に意味はないので例として書いているコードに数値リテラルを使っているけど、現実のコードの中に無意味なものなんて一つもないはずなので、とにかく適切に名前を付けること
×ダメ
int users[100];
○良い
別に #define でも enum でも static const int でも何でもいい
#define MAX_USERS 100 . . . int users[MAX_USERS];
×ダメ
名前を付けたからといって要素数やサイズを再計算したりしないこと
int users[MAX_USERS]; memset( users, 0, sizeof( int ) * MAX_USERS );
数値リテラルを自分で計算しない
コード中にいきなりわけのわからない数値リテラルが出てきても読み手は困るので、自分で勝手に計算をまとめてしまわない方がいいでしょう
×ダメ
s = d * 86400;
×ダメ
計算をまとめてしまわないのは良いのですが、数値リテラルをそのまま使ってはダメ
s = d * (24*60*60);
×ダメ
数値に名前を付けているのは良いのですが、自分で計算してはダメ
#define SECONDS_OF_A_DAY 86400 . . . s = d * SECONDS_OF_A_DAY;
○良い
#define SECONDS_OF_A_DAY (24*60*60) . . . seconds = days * SECONDS_OF_A_DAY;
○良い
#define SECONDS_OF_A_MINUTE 60 #define MINUTES_OF_AN_HOUR 60 #define HOURS_OF_A_DAY 24 #define SECONDS_OF_A_DAY (HOURS_OF_A_DAY*MINUTES_OF_AN_HOUR*SECONDS_OF_A_MINUTE) . . . seconds = days * SECONDS_OF_A_DAY;
×ダメ
100 という数値に意味があるときに 100-1 を勝手に計算して 99 と書くようなことはしてはいけません
int foo[100]; . . . foo[99] = 0;
×ダメ
100 という数値に意味があるときに 100-1 を 99 ではなく 100-1 と書くことは良いのですが、しかし数値リテラルをそのまま使うのはダメですし、配列の要素数を自分で指定するのもダメです
意味があるのなら適切な名前を付けましょう
int foo[100]; . . . foo[100-1] = 0;
×ダメ
数値リテラルに名前を付けることはとても良いことですが、配列の要素数を自分で指定してはいけません
#define MAX_FOO 100 int foo[MAX_FOO]; . . . foo[MAX_FOO-1] = 0;
○良い
#define MAX_FOO 100 int foo[MAX_FOO]; . . . foo[elementsof( foo )-1] = 0;
×ダメ
例えば '0' 〜 '9' をチェックするときに勝手に自分で文字コードを解釈して下記のようにしてはいけません
-1 と 10 と 0x30 はどこから出てきたのでしょうか
int c = cc - 0x30; if(( c > -1 )&&( c < 10 )){ ; }
○良い
'0' 〜 '9' に意味があるのなら '0' と '9' を使うこと
(ただし、この例の場合は本当は isdigit を使うこと)
if(( cc >= '0' )&&( cc <= '9' )){ ; }
暗黙の演算子の優先順位を期待して括弧を省略しない
×ダメ
foo >> 1*2 + 3
○良い
foo >> ((1*2)+3)
×ダメ
foo / bar * baz
○良い
( foo / bar ) * baz
×ダメ
if( foobar == FOO || foobar == BAR ){
}
○良い
if(( foobar == FOO )||( foobar == BAR )){
}
×ダメ
if(( foobar == FOO )||( foobar == BAR )&& bar ){
}
○良い
if(( foobar == FOO )||(( foobar == BAR )&& bar )){
}
でも比較演算子の左右は別に括弧を付けなくてもいい
こんなのはダメじゃないけど冗長
if(( foo + 1 ) < FOO_MAX ){ }
○良い
if( foo + 1 < FOO_MAX ){ }
意味もないのに余計な括弧を付けない
×ダメ
char* p = &( foo[0] );
○良い
char* p = &foo[0];
×ダメ
char* p = &( foo[0].bar );
○良い
char* p = &foo[0].bar;
bool値を 0 や 1 と比較しない
×ダメ
BOOL foo = FALSE; . . . if( foo == 0 ){ } else{ }
△まだダメ
BOOL foo = FALSE; . . . if( foo == FALSE ){ } else{ }
○良い
BOOL foo = FALSE; . . . if( !foo ){ } else{ }
単独の == 0, != 0 は省略する
冗長だから省略した方が良い
△ダメじゃないけど冗長
if( n != 0 ){ }
○良い
if( n ){
}
△ダメじゃないけど冗長
if( n == 0 ){ }
○良い
if( !n ){
}
NULL との明示的な比較も冗長だから省略した方が良い
△ダメじゃないけど冗長
if( p == NULL ){ }
○良い
if( !p ){
}
△ダメじゃないけど冗長
if( p != NULL ){ }
○良い
if( p ){
}
例え一行であったとしても if, for, while などの後の {} を省略しない
×ダメ
BOOL fSuccess = TRUE; if( !p ) fSuccess = FALSE; if( fSuccess ){ }
○良い
BOOL fSuccess = TRUE; if( !p ){ fSuccess = FALSE; } if( fSuccess ){ }
for は回数か範囲、whileは条件
回数か範囲を指定したループを書きたいときには for 、条件を指定したループを書きたいときには while と必ず使い分けること
×ダメ
回数でも範囲でもないなら for は使わない。条件を指定したループは while を使うこと
読み手に for は回数か範囲、while は条件と約束したら、常にそれを守る。見た目のシンプルさはどうでもいい
for(; foo ; foo = foo->get_next()){
.
.
.
}
○良い
while( foo ){
.
.
.
foo = foo->get_next();
}
この例のような「リストを辿るときは for を使う」と決めるなら for でも良い。ただし必ず for を使うこと。一部は while を使わないといけないというなら for を使ってはダメ。全部を while にすること
これは要するに「指定回数繰り返す」「指定の範囲を繰り返す」「特定条件の間繰り返す」「リストを辿る」といった同じ目的には同じものを使えということ
だから単純に指定回数ループするだけのようなときには必ず for を使うこと。もし単純な指定回数のループに while を使うと、コードを読む人が for を使わない意図が何かあるのではないかと期待して読むことになるので、そういう意図が無いなら余計なことをせず素直に for を使うこと
×ダメ
int n = 0; while( n < 100 ){ . . . ++n; }
○良い
本当はループ回数が数値リテラルなんてのはダメ
for( int n = 0; n < 100; ++n ){ . . . }
○良い
for( std::vector<int>::const_iterator p = foo.begin(); p != foo.end(); ++p ){ . . . }
forループのループカウンタをいじらない
ループカウンタをいじるなら for ではなく while を使うこと
×ダメ
for( int n = 0; n < 100; ++n ){ . . . if( foo( n )){ ++n; } }
○良い
本当はループ回数が数値リテラルなんてのはダメ
int n = 0; while( n < 100 ){ . . . if( foo( n )){ ++n; } ++n; }
for のループカウンタは 0→回数とする
指定回数の繰り返しのために for を使うとき、ループカウンタは「0→回数」とすること
「回数-1→0」はダメ
×ダメ
for( int n = 100-1; n >= 0; ++n ){ . . . }
×ダメ(バグ)
「回数-1→0」というループではループカウンタが unsigned だと、そのままではループ終了条件の n >= 0 が常に真なので無限ループになります
for( unsigned int n = 100-1; n >= 0; ++n ){ . . . }
従って、ループカウンタが unsigned のときには判定のときにループカウンタを signed にキャストするか「回数→1」のループにする必要があります
for( unsigned int n = 100-1; ( signed int )n >= 0; ++n ){ . . . }
for( unsigned int n = 100; n > 0; ++n ){ unsigned int m = n-1; . . . }
このようにループカウンタが signed か unsigned かでループを「回数-1→0」「回数→1」などと書き方を変える必要があり、間違いの元になります
同様の問題は「0→回数」という書き方でもループ回数が INT_MAX や UINT_MAX などの場合に発生しますが、通常はループ回数がそのような回数になることはないでしょう
というわけで、for ループは必ず「0→回数」と書きましょう
○良い
for( int n = 0; n < 100; ++n ){ . . . }
○良い
指定回数の逆順ループを書きたいときは下記のように書きましょう
for( int n = 0; n < 100; ++n ){ int m = 100-n-1; . . . }
全く最適化が無いと考えた場合「0→回数」というループよりも「回数-1→0」というループの方が若干速いかもしれません
しかし、そんなことはコンパイラにまかせましょう
パフォーマンスに影響があってどうしても「回数-1→0」にしないといけないというようなところは当然「回数-1→0」というループにすべきですが、そういう特別な場合にはコメントを書きましょう
そういった特別な場合以外は「0→回数」というループにしましょう
forループのループカウンタをループの後で使いたいようなときは while にする
×ダメ
ローカル変数を初期化してない
int n; for( n = 0; n < 100; ++n ){ if( ... ){ break; } }
△ダメじゃないけど
int n = 0; for( n = 0; n < 100; ++n ){ if( ... ){ break; } }
△ダメじゃないけど
int n = 0; for(; n < 100; ++n ){ if( ... ){ break; } }
×ダメ
ループ回数を二重に設定している
int m = 100; for( int n = 0; n < 100; ++n ){ if( ... ){ m = n; break; } }
×ダメ
単純な指定回数のループじゃないし、無駄に冗長
int m = 100; for( int n = 0; n < m; ++n ){ if( ... ){ m = n; break; } }
○良い
小細工をせず素直に while にする
int n = 0; while( n < 100 ){ if( ... ){ break; } ++n; }
変数名は正しい英語で
間違ったスペルはダメ
日本語のローマ字表記もダメ
わからなかったら調べること
奇抜なことはしない
文法が許すからといって何をしてもいいわけではありません
テンプレートやマクロを使って奇抜なことはしないのがいいでしょう
よくできる人ほど少し書けるようになってくると boost の真似をしたくなったりするでしょうけれども、そういうことは自分用のライブラリでやりましょう
プロジェクトのコードでやるときは自分ができる知っているということよりも、プロジェクトのほかの人が理解できるかどうかを考えて書きましょう
operator&& operator|| operator, をオーバーロードしない
operator&& operator|| operator, をオーバーロードする機会はないでしょうが、絶対にオーバーロードしてはいけません。絶対
特に operator&& operator|| はオーバーロードしてしまうと、文法としての && || の動作とは異なり、条件の成否に関わらず常に両辺が評価されてしまうことになります。決してオーバーロードしてはいけません
operator, はもしかすると行列クラスの初期化のためなどにオーバーロードしたくなるかもしれません。しかし、オーバーロードしてはいけません
見た目のおもしろさよりも読み手の読み易さを優先しましょう
#endif の横に対応する #if の条件をコメントで書く
#if 〜 #endif の間はインデントしない方がいいけれど、そうすると #if 〜 #endif の対応が分かり辛くなるので、#endif の横には一目で対応する #if がわかるようにいつもコメントを書いておいた方がいい
△別にダメじゃないけど
#if defined( __cplusplus ) . . . #endif
○良い
#if defined( __cplusplus ) . . . #endif // __cplusplus
#endif の横にコメントを書くときも // を付ける
×ダメ
#endif の横は行末まで無視されるので下記のようにしても文法的には間違いではないけど、ダメ
#if defined( __cplusplus ) . . . #endif __cplusplus
○良い
#if defined( __cplusplus ) . . . #endif // __cplusplus
無限ループは while か do-while か for か、どれか一つだけをを使う
混在は読み手を混乱させます。同じ目的には同じもを使いましょう
while( true ){ . . . }
do{ . . . }while( true );
for(;;){
.
.
.
}
そもそもあまり無限ループを使わないこと
インクリメント・ディクリメントは前置
特に理由の無い限りすべてのインクリメント・ディクリメントは前置を使う
×ダメ
i++;
○良い
++i;
無駄なテンポラリオブジェクトは排除する
組み込み型の場合には実際にはこの違いで無駄なコードが生成されるようなことはないのですが、普段からこういった細かいところも疎かにしないようにという意味で、徹底して前置を使うようにクセを付けた方がいいでしょう
比較演算子の左辺に定数を持ってこない
読みづらいからダメ。英語の語順に合わせて
×ダメ
if( 1 == n ){ }
○良い
本当は数値リテラルに名前を付けないとダメ
if( n == 1 ){ }
if( n = 1 ) と間違うだとかそんなことはコンパイラが警告してくれるから気にしなくていい
this は常に比較演算子の左辺に置く
読みづらいからダメ。英語の語順に合わせて
×ダメ
CFoo& operator=( const CFoo& rhs ){ if( &rhs != this ){ . . . } return *this; }
○良い
CFoo& operator=( const CFoo& rhs ){ if( this != &rhs ){ . . . } return *this; }
ハンガリアン記法は使わない
ハンガリアン記法はもうあんまり使わない方がいいかな
ただ、コードの文脈から外れるようなところにあるオブジェクトの名前には何かその印を付けておくといいでしょう
グローバルオブジェクトには g_ を付ける
public でないメンバオブジェクトには m_ を付ける
const オブジェクトなら g_, m_ ではなく c_ を付ける
○良い
CFoo g_foo;
static CFooBar g_foobar;
○良い
const int c_foo[FOO_MAX] = { . . . }; static const int c_bar[BAR_MAX] = { . . . }; const int CFoo::c_baz[BAZ_MAX] = { . . . };
○良い
class CFoo { int m_foo; . . . };
ネストしたブロック内にだけ見えるような static なオブジェクトは持たない
ネストしたブロック内にだけ見えるような static なオブジェクトは持たないこと
開発中に一時的に書くのは別にいいけど、実際に有効なコードとしてはダメ。どこかのクラスのメンバにするなりグローバルに出すなり適切な扱いをすること
×ダメ
{ . . . { static int s_foo = 0; static const char* c_names[] = { . . . }; if( s_foo ){ . . . } } . . . }
変数宣言などを無理に揃えない
ソースコード中では別に揃えなくていいです。ドキュメントを書きましょう
×ダメ
class CFoo { int m_foo; // コメント1 very_very_very_very_very_very_very_very_very_very_very_long_type_name m_very_very_long_property_name; // コメント2 int m_baz; // コメント3 . . . };
○別に気にしないでいい
class CFoo { int m_foo; // コメント1 very_very_very_very_very_very_very_very_very_very_very_long_type_name m_very_very_long_property_name; // コメント2 int m_baz; // コメント3 . . . };
意味も無く this-> という表記を使わない
何の意味もないのに this-> という表記を使ったりしないこと
使っていいのは必要なときだけ
×ダメ
int CFoo::get_foo() const { return this->m_foo; }
○良い
int CFoo::get_foo() const { return m_foo; }
○良い
int CFoo::get_foo() const { return (this->*m_callback)(); }
関数形式のマクロの引数には必ず括弧を付ける
式に置換される関数形式のマクロの引数には必ず括弧を付けるようにしましょう
×ダメ
#define elementsof( a ) (sizeof(a)/sizeof(a[0]))
○良い
#define elementsof( a ) (sizeof((a))/sizeof((a)[0]))
式に置換されるマクロは必ず全体を括弧で囲む
式に置換されるマクロは必ず全体を括弧で囲むようにしましょう
×ダメ
#define elementsof( a ) sizeof((a))/sizeof((a)[0])
○良い
#define elementsof( a ) (sizeof((a))/sizeof((a)[0]))
×ダメ
#define invalid_foo -1
○良い
#define invalid_foo (-1)
適切に const を付ける
参照するだけで変更しないメンバ関数やグローバルなオブジェクトや関数の引数などにはきちんと const を付けましょう
特に参照は const参照と非const参照では文法的にも扱いが異なりますので気をつけましょう
例えば「const参照は一時オブジェクトを受け取れるが、非const参照は一時オブジェクトを受け取れない」などといった違いがあります
予約されてる名前を勝手に使わない
import, export, min, max などはうっかり使ってしまいがちなので気をつけましょう
×ダメ
struct range_t { long min; long max; };
×ダメ
result_t CFoo::export( const export_params_t& params ){ . . . }
基本的に C スタイルのキャストは使わない
基本的に C スタイルのキャストは使わないクセを付けた方がいいでしょう
static_cast か reinterpret_cast を使いましょう。普通はそれで事足りるはずです
dynamic_cast を使っていいかどうかはプロジェクトによって判断が分かれるので、プロジェクトの方針に従ってください
static_assert にすべきものを assert にしない
assert の条件が定数になるようなものは static_assert にできます
static_assert を使えばコンパイル時にコンパイルエラーとして検出できます
×ダメ
DYNAMIC_ASSERT( !( sizeof( foo ) % sizeof( DWORD )), "あれ、foo のサイズがおかしいよ" );
○良い
STATIC_ASSERT( !( sizeof( foo ) % sizeof( DWORD )), "あれ、foo のサイズがおかしいよ" );
標準の正式な static_assert はまだサポートされていませんが、テンプレートや要素数 0 の配列宣言などを利用して似た機能のマクロは書けます
Visual C++ の malloc.h にあるもの
#define _STATIC_ASSERT(expr) typedef char __static_assert_t[ (expr) ]
boost の static_assert.hpp にあるもの
template <bool x> struct STATIC_ASSERTION_FAILURE; template <> struct STATIC_ASSERTION_FAILURE<true> { enum { value = 1 }; }; template<int x> struct static_assert_test{}; #define BOOST_STATIC_ASSERT( B ) typedef static_assert_test<sizeof(STATIC_ASSERTION_FAILURE<(bool)(B)>)> static_assert_t
assert の条件を明示的に常に false で使うような場合には専用のマクロなどを用意する
assert の条件を明示的に常に false で使うような場合には専用のマクロなどを用意すること
例えばこんな感じで
×ダメ
if( ... ){ DYNAMIC_ASSERT( FALSE, "万一ここに来ちゃったら警告を出すよ" ); }
○良い
#define DYNAMIC_ASSERT_ALWAYS( message_text ) \ __pragma( warning( push ))\ __pragma( warning( disable : 4127 ))\ DYNAMIC_ASSERT( FALSE, message_text )\ __pragma( warning( pop )) . . . if( ... ){ DYNAMIC_ASSERT_ALWAYS( "万一ここに来ちゃったら警告を出すよ" ); }
non-POD な型を memset, memcpy に渡さない
仮想関数などが無く、実際には問題にならないとしても、ダメです
実際に問題になるかどうかは別にして、規格上 memset や memcpy に渡していいのは POD だけです
特に初期化が面倒だからといってコンストラクタでや代入演算子などで this を memset や memcpy に渡すようなことは、実際に問題にならないとしても、してはいけません
×ダメ
class CFoo { . . . CFoo(){ memset( this, 0, sizeof(*this)); } CFoo( const CFoo& rhs ){ memcpy( this, &rhs, sizeof( *this )); } CFoo& operator=( const CFoo& rhs ){ if( this != &rhs ){ memcpy( this, &rhs, sizeof( *this )); } return *this; } bool operator==( const CFoo& rhs ){ return (( this == &rhs )|| !memcmp( this, &rhs, sizeof( *this ))); } };
delete, delete[] を直接呼ばない
メモリ管理など低レベルなコード以外では、delete, delete を直接呼ばない方がいいでしょう
例えば delete, delete に渡されているオブジェクトの型を知りたい、というようなときにも delete, delete を直接呼ばずに下記のようなラッパ関数だけを使うようにしてあれば、とても簡単に実現できますが、delete, delete を直接呼んでいるとこのような対応が難しいためです
template<class T> void my_delete( T& p ){ delete p; p = NULL; } template<class T> void my_delete_array( T& p ){ delete [] p; p = NULL; } template<> void my_delete<void*>( void*& p ); template<> void my_delete_array<void*>( void*& p );
↓
template<class T> void my_delete( T& p ){ // ここに何かを仕込むのなんて簡単 delete p; p = NULL; } template<class T> void my_delete_array( T& p ){ // ここに何かを仕込むのなんて簡単 delete [] p; p = NULL; } template<> void my_delete<void*>( void*& p ); template<> void my_delete_array<void*>( void*& p );
delete, delete[] は直接呼ばずラッパを用意してそれを使うようにしましょう
浮動小数点数の計算結果を特定の数値と == != で比較しない
例えば FLT_MIN や FLT_MAX などを利用して意味の無い値であることを確認するために == != を使うのは構いませんが、計算結果を 0.0f と比較したりしないこと
浮動小数点数の計算には常に誤差があるという前提でコードを書くべきです
△可
float min_value = FLT_MAX; for( size_t n = 0; n < elementsof( foo ); ++n ){ if( foo[n] < min_value ){ min_value = foo[n]; } } if( min_value == FLT_MAX ){ ... }
×ダメ
float distance = sample_calc_distance( pt1, pt2 ); if( distance == 0.0f ){ }
○良い
float distance = sample_calc_distance( pt1, pt2 ); if( sample_fabs( distance ) <= FLT_EPSILON ){ }
FLT_MIN や FLT_MAX などを特別な意味の値として使うとしても、そのまま使わずに適切な名前を付けて使うこと
×ダメ
適切な名前を付けること
void CFoo::foo( int bar, float baz ){ if( baz == FLT_MIN ){ baz = get_default_value( bar ); } . . . }
○良い
#define FOO_BAZ_USEDEFAULT FLT_MIN . . . void CFoo::foo( int bar, float baz ){ if( baz == FOO_BAZ_USEDEFAULT ){ baz = get_default_value( bar ); } . . . }
浮動小数点数は値を浮動小数点数用のレジスタに置くか置かないかといった微妙なことでも結果が異なる場合があるので、本当に気をつけること
例えば、VC6 とかだと ( cos(1.0) == cos(1.0)) が false になるときもある
浮動小数点数をキャストで安易に整数化しない
整数になるはずの計算でも、誤差があるために整数の境界に僅かに届かない場合があります
×ダメ
int _5 = ( int )( 2.0 / 0.4 );
△んー
int _5 = ( int )(( 2.0 / 0.4 ) * ( 1.0 + DBL_EPSILON ));
○良い
専用の整数化機能を使う
int _5 = sample_ftoi( 2.0 / 0.4 );
goto の作法
「goto を使うな!」というのはよく言われることではありますが、必ずしも一切使ってはいけないということではありません
しかし同時に「少しなら goto も使ってもいい」とか「文法にあるんだからどんな使い方でも気にせず goto を使っていい」というものでもありません
goto は使っても構いませんが、goto には使う上で守るべきマナーというものが幾つかあります
ブロックの出口は唯一つに
goto を使うなと言われる大きな理由の一つにブロックの出口がたくさんできてしまうという問題があります
しかも goto の場合にはその飛び先もたくさんできてしまいます
return, break, continue が goto に比べていくらかマシだというのは return, break, continue ではブロックの出口が増えることはあってもその飛び先は常に唯一つに決まっているからです
なぜブロックの出口をいくつも作ってはいけないのか
その答えはコードの構造化された状態を崩すからです
構造化されていないコードの何がいけないのかと言えばこれは簡単なことで、ただプログラマがそんなにたくさんの状態を把握しきれないからというだけのことです
つまりプログラマがコードを読み難くなるから goto を使うなということです
ですからコードが読み難くならないのであれば、あるいは返ってコードが読み易くなるのであれば、goto を使っても全く構わないわけです
上方への goto は禁止
goto を使ってもいいのはその飛び先がコードの下方にある場合だけに限定されるべきです
上方への goto でループを構成するようなコードは決して書いてはいけません
×ダメ
こんなのは絶対にダメ!
FOO_CHECK:; if( foo()){ . . . if( bar()){ goto FOO_CHECK; } . . . }
ループを書きたければ for や while を使うべきです
「for や while では実現できないループだから仕方が無い」というようなことは決してありません
必ず goto を使わなくても済む方法があります
別ブロックへの goto は禁止
goto の飛び先が下方であっても他所のブロックに飛び込むようなことは決してしてはいけません
×ダメ
こんなのはダメ。絶対
if( foo()){ . . . if( bar()){ goto QUX_BLOCK; } . . . if( baz()){ . . . if( qux()){ QUX_BLOCK:; . . . } else{ . . . } } }
特に if 節から else 節などに goto でジャンプするなんていうのは言語道断です
×ダメ
こんなのも絶対にダメ!
if( foo()){ . . . if( bar()){ goto ELSE_BLOCK; } . . . } else{ ELSE_BLOCK:; . . . }
このような goto を使いたいという場合には関数を分割できないかどうか考えるべきでしょう
goto での case 間移動は禁止
switch-case でこのように goto を使って case間を移動するようなことはすべきではありません
×ダメ
ループを使え
switch( foo ){ case FOO: CASE_FOO:; { . . . if( bar()) goto CASE_BAR; . . . } break; case BAR: CASE_BAR:; { . . . if( baz()) goto CASE_BAZ; . . . } break; case BAZ: CASE_BAZ:; { . . . if( foo()) goto CASE_FOO; . . . } break; }
case 間を移動したい場合にはループを使うべきです
またこのような goto を使いたいという場合には関数を分割することも考えるべきでしょう
多重ループからの脱出のための goto は可
多重ループからの脱出のために goto を使うことは比較的問題が少ないと言えるでしょう
△可
for( int y = 0; y < cy; ++y ){ for( int x = 0; x < cx; ++x ){ if( foo()){ goto end_of_this_loop; } . . . } } end_of_this_loop:;
ただし、このような goto を使う場合このループには break や continue などを使うべきではありません
ブロック冒頭での検査のための goto は可
関数の先頭などブロック冒頭での検査のために goto を使うことは比較的問題が少ないと言えるでしょう
△可
FOO* get_foo(){ FOO* pFoo = NULL; if( !m_bar ){ goto end_of_this_function; } . . . end_of_this_function:; return pFoo; }
ただし、このような goto を使う場合この関数では関数途中での return は使うべきではありませんし、goto で抜けるのはそのブロックだけにした方が望ましいでしょう
case の最後で break しないときは必ずコメントを書く
case の最後で break しないときは必ずコメントを書きましょう
×ダメ
switch( mode ){ case FOO: mode = BAR; case BAR: . . . }
○良い
switch( mode ){ case FOO: mode = BAR; // break しない case BAR: . . . }
ただ「break しない」というだけでなく「○○だから break しない」というようにその理由も書いておくとより良いと思います
ただし、コメントを書けばいいというものでもありません
下記のように条件によって break したりしなかったりといったことはやめましょう
×ダメ
switch( foo ){ case FOO: . . . if( !bar()){ break; } // break しないよ case BAR: . . . break; }
このような場合には各case を関数に分割できないかどうか考えるべきでしょう
continue はなるべく使わない
continue はなるべく使わない方がいいでしょう
使うにしても次に挙げるようなところには特に気をつけましょう
無駄に continue しない
例えば 0 でない要素の数を数えるのにこんなループは書かない
×ダメ
size_t n = 0; for( std::vector<int>::iterator p = a.begin(); p != a.end(); ++p ){ if( !*p ){ continue; } ++n; }
○良い
size_t n = 0; for( std::vector<int>::iterator p = a.begin(); p != a.end(); ++p ){ if( *p ){ ++n; } }
continue を使わないとネストが深くなりすぎたりといったことがあるなら関数に分割できないかどうか検討すべきです
switch-case の中で continue しない
×ダメ
while( get_msg( &msg )){ switch( msg.message ){ case FOO: if( foo_cancelled( &msg )){ continue; } . . . break; } }
break すべきところで continue しない
例えば最初の 0 でない値を得るのに普通はこんなループは書かない
×ダメ
int value = 0; for( std::vector<int>::iterator p = a.begin(); p != a.end(); ++p ){ if( !*p || value ){ continue; } value = *p; }
○良い
int value = 0; for( std::vector<int>::iterator p = a.begin(); p != a.end(); ++p ){ if( *p ){ value = *p; break; } }
このコードを while で書き換えると下記のようなコードになりますが
下記のコードでは continue するときに ++p と次のループのために continue しないときと同じ処理をしています
こんなのは特にダメです
×ダメ
int value = 0; std::vector<int>::iterator p = a.begin(); while( p != a.end()){ if( !*p || value ){ ++p; continue; } value = *p; ++p; }
ループの中に無条件な break や continue を書かない
例えば最初の 0 でない値を得るのに普通はこんなループは書かない
×ダメ
int value = 0; for( std::vector<int>::iterator p = a.begin(); p != a.end(); ++p ){ if( !*p ){ continue; } value = *p; break; }
○良い
同じ意味なのですからわざわざ変な書き方をするのはやめてこのように普通に書きましょう
int value = 0; for( std::vector<int>::iterator p = a.begin(); p != a.end(); ++p ){ if( *p ){ value = *p; break; } }
巨大な switch-case を書かない
同じ関数であっても呼び出されたときの内部的な状態に応じて全く異なる動作をする関数、そしてまた同じ関数であっても呼び出されたときに外部から指定されるパラメータに応じて全く異なる動作をする関数、このような関数が使われるのは例えば予め登録しておいたコールバック関数が様々な状態に応じて呼び出されるといったような場合が当てはまると思います
具体的には Windows のウィンドウプロシージャがそうですね
Fig.1
LRESULT WindowProc_Sample( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ){ LRESULT lresult = 0; switch( uMsg ){ case WM_CREATE: { LPCREATESTRUCT pcs = ( LPCREATESTRUCT )lParam; ... } break; case WM_DESTROY: ... break; case WM_COMMAND: { UINT wNotifyCode = ( UINT )HIWORD(wParam); UINT wID = ( UINT )LOWORD(wParam); HWND hwndCtl = ( HWND )lParam; ... } break; case WM_LBUTTONDOWN: { UINT fwKeys = ( UINT )wParam; POINT pt = { ( LONG )( SHORT )LOWORD(lParam), ( LONG )( SHORT )HIWORD(lParam) }; ... } break; . . . } return lresult; }
このような関数を実装するときに Fig.1 のように各 case に直に処理を書いてしまって結果として関数の中身は巨大な switch-case が1つだけというようなコードになってしまっている人がいます
こんなのはダメです
なぜダメなのでしょう
考えてみてください
この switch-case の個々の case は本来はそれぞれ独立した関数であるべきものがインターフェイスの仕様の都合によって仕方なく1つの関数にまとめられているだけではないですか?
例えば Fig.1 では uMsg が WM_CREATE のときにはその WM_CREATE の case の部分だけが処理されればよくて他の case の部分は全く必要のないものですよね
また uMsg が WM_DESTROY のときにはその WM_DESTROY の case の部分だけが処理されればよくて他の case の部分は全く必要のないものですし、他の case も同じですね
それぞれの case は全く関係のないコードなのですから、それなら本来は別々の関数に分けるべきなのです
Fig.2
LRESULT OnCreate( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ){ ... } LRESULT OnDestroy( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ){ ... } LRESULT OnCommand( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ){ ... } LRESULT OnLButtonDown( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ){ ... }
そして巨大な switch-case があった元の関数は単純に振り分ける処理だけに専念するようにします
Fig.3
LRESULT WindowProc_Sample( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ){ LRESULT lresult = 0; switch( uMsg ){ case WM_CREATE: lresult = OnCreate( hwnd, uMsg, wParam, lParam ); break; case WM_DESTROY: lresult = OnDestroy( hwnd, uMsg, wParam, lParam ); break; case WM_COMMAND: lresult = OnCommand( hwnd, uMsg, wParam, lParam ); break; case WM_LBUTTONDOWN: lresult = OnLButtonDown( hwnd, uMsg, wParam, lParam ); break; . . . } return lresult; }
これを面倒くさがるから巨大な switch-case ができてしまうのです
もちろんこれはウィンドウプロシージャのように外部から指定されたパラメータによって処理を振り分ける場合だけでなく内部的な状態変数によって処理を振り分けるような場合も同様です
Fig.4
void CSample::DoSomething(){ switch( m_mode ){ case SAMPLE_MODE_AAA: ... break; case SAMPLE_MODE_BBB: ... break; case SAMPLE_MODE_CCC: ... break; case SAMPLE_MODE_DDD: ... break; . . . } }
こんな場合も次のように振り分け部分と実際の処理部分とに必ず分けるようにしましょう
Fig.5
void CSample::OnAAA(){ ... } void CSample::OnBBB(){ ... } void CSample::OnCCC(){ ... } void CSample::OnDDD(){ ... }
Fig.6
void CSample::DoSomething(){ switch( m_mode ){ case SAMPLE_MODE_AAA: OnAAA(); break; case SAMPLE_MODE_BBB: OnBBB(); break; case SAMPLE_MODE_CCC: OnCCC(); break; case SAMPLE_MODE_DDD: OnDDD(); break; . . . } }
これを面倒くさがらないでください
面倒くさいわけではなく「ある case の後に続けて別の case の処理を実行したいから分割できない」とか「状況に応じて case 間をいろいろに移動するので分割できない」と言う人もいるかもしれませんが、しかし、そのような場合には処理を振り分ける関数内で適切なループを書きましょう
Fig.7
void CSample::DoSomething(){ int mode; do{ mode = m_mode; switch( m_mode ){ case SAMPLE_MODE_AAA: OnAAA(); break; case SAMPLE_MODE_BBB: OnBBB(); break; case SAMPLE_MODE_CCC: OnCCC(); break; case SAMPLE_MODE_DDD: OnDDD(); break; . . . } }while( mode != m_mode ); }
あるいはこのようにしてもいいでしょう
Fig.8
BOOL CSample::DoSomething_Dispatch(){ BOOL fDone = TRUE; switch( m_mode ){ case SAMPLE_MODE_AAA: fDone = OnAAA(); break; case SAMPLE_MODE_BBB: fDone = OnBBB(); break; case SAMPLE_MODE_CCC: fDone = OnCCC(); break; case SAMPLE_MODE_DDD: fDone = OnDDD(); break; . . . } return fDone; } void CSample::DoSomething(){ while( !DoSomething_Dispatch()){ ; } }
あるいはまた例えば OnAAA から OnBBB を呼び出すなどの方法でも対処できるでしょう
いずれにせよ巨大な switch-case を書く言い訳にはなりません
さて、ここまでは switch-case を例に使ってきましたが、このようなことはもちろん switch-case という構文だけに限ったことではありません
例えば次のようなコードではどうでしょう
Fig.9
void CSample::DoSomething(){ if( IsAAA()){ ... // AAA のとき固有の処理 ... } else{ ... } . . . // 何か共通の処理 . . . if( IsAAA()){ ... // AAA のとき固有の処理 ... } else{ ... } . . . // 何か共通の処理 . . . if( IsAAA()){ ... // AAA のとき固有の処理 ... } else{ ... } . . . // 何か共通の処理 . . . if( IsAAA()){ ... // AAA のとき固有の処理 ... } else{ ... } }
この CSample::DoSomething というメソッドは IsAAA が真を返すときと偽を返すときとで全く別のことをするように書かれています
しかし、このようにそれぞれの場合で全く別のことをするならそれを一緒の関数に書いておく理由は何もありません
つまりこれも上で述べてきた場合と同様に適切に分割すべき例だということですね
処理を分けるのが面倒だと言わずに分割すべきところはきちんと分割しましょう
この「処理を適切な単位に分割して関数とする」というのはプログラミングの初歩の初歩ですから面倒くさがらずに実践するように心掛けてください