[ C++で開発 ]
自分用コーディング標準(C++)
C++コーディング標準はいくつかありますが、どうもピンとくるのがないので良いとこ取りで自分用コーディング標準をまとめます。
スタイル編
ファイル構成
C++は、マルチパラダイム言語です。オブジェクト指向プログラミングを中心に据える場合はクラスが構成要素の中核となりますが、ジェネリックス(テンプレート)プログラミングを中心に据える場合はテンプレート集が構成要素の中核となります。もちろん構造化プログラミングを中心に据える場合はフリー関数が構成要素の中核となります。
クラス中心方式を採用する場合
主にオブジェクト指向プログラミングを行う場合のファイル構成法です。
- 1クラスを1対のファイル(ヘッダーファイルおよびソースファイル)に記述する
- あるクラス内でしか利用しないクラスは、内部クラスにしてもよい
- ファイル名は、クラス名を基幹名(basename)とし、拡張子は以下の通りとする
- ヘッダーファイル:.hhまたは.h
- ソースファイル:.ccまたは.cpp
- 名前空間毎に異なるディレクトリとする。ディレクトリ階層は名前空間階層と一致させ、ディレクトリ名は名前空間名に基づく
- 名前空間の外に公開するヘッダーファイルと、名前空間内のみで使用するヘッダーファイルは分けて管理する。公開ヘッダーファイルは非公開ヘッダーファイルに依存しないようにする。
ジェネリックス中心方式を採用する場合
ヘッダーファイル
- インクルードガードを必ず定義する。シンボル名は、名前空間とクラス名を全て大文字で表現し、要素名をアンダースコアでつなぐ。
- 代替案)#pragma onceを使う。これは非標準でありVC++方言のため、一時は使用を推奨しなかったが、GCCを始め大半のコンパイラが対応しているので使用してもよいと考えるようになった。むしろインクルードガードのバッティングの方が発生しやすいかも。
- 代替案)UUIDで生成した擬似一意な識別子をシンボル名に使用する。バッティング可能性はほぼゼロになるが、見てくれが悪いほか、アウターインクルードガードに使用しずらい。
- ヘッダーファイルでは、usingディレクティブを使用しない
- 事前宣言で済む型については#includeでヘッダーファイルを取り込まない
- インクルードの順序関係を要求するヘッダーファイルにしない
- インクルード時には名前空間に対応したディレクトリ名を含めて#include文に記述する。これはヘッダーファイル名が重なることで発生する問題を防ぐためである。
クラス定義のレイアウト
クラス定義のレイアウトにはいくつか流儀があります。可視性制御順(public/protected/private)に並べるもの、意味順に並べるもの(構築系、アクセッサ系、ミューテータ系)、などです。ここでは、後者の意味順に並べる方法のうち、「なるほど、これはよい方法だ」とうなった記述方法を挙げます。
- クラス定義のレイアウトは、コンストラクタ/デストラクタ、操作メソッド、属性メソッド、イテレーション、状態、実装、メンバー変数、実装を提供しないもの、の順に並べる(by
Matthew Wilson, "Imperfect C++"より)
class Sample {
// コンストラクタ
public:
Sample();
Sample(Sample& r);
protected:
Sample(ResourceImpl* rimpl);
// デストラクタ
public:
virtual ~Sample();
// 操作
public:
void open();
void open(ResourceImpl* rimpl);
void close();
// 属性
public:
size_t getSize() const;
protected:
void setSize(size_t const size);
...
// 非実装提供
private:
Sample& operator=(Sample& rhs);
};
もし、よくあるpublic、protected、privateの順に定義するやり方でレイアウトしていたら、publicなメソッドをprivateに変更すると、定義行の位置が大きく変わり、バージョン管理システムでの差分が見えなくなるという問題がある。
ソースファイル
- 自身のプロトタイプ宣言(クラス定義)をしているヘッダーファイルを最初にインクルードする
- 同じ名前空間(Java流に言えばパッケージ)に属するヘッダーファイルのインクルードには""を、他の名前空間に属するヘッダーファイルのインクルードには<>を使用する。
インデント
- インデントの1レベルは4空白とし、タブコードは使わない
- Eclipse CDTのソースコード・フォーマッタでK&Rスタイルに合わせる
命名編
関数、メンバ関数
- 先頭を小文字とするキャメル・タイプ(小文字で開始し、単語と単語のつなぎは次の単語の先頭を大文字とする。なお、すべて大文字の単語も小文字で扱う。例:getHttpStatus
)。
ローカル変数
- 先頭を小文字とするキャメル・タイプ。ループ制御変数のi, j, kなどは慣習に従って使ってよい。
- 不変な変数は、定数の命名に従う。(検討中)
ユーザー定義型(class、enum、struct)
※ 標準I/OストリームやSTLの命名とは異なる
名前空間
- すべて小文字。極力複数単語をつなげない。どうしてもつなげる場合は標準I/Oストリームのように小文字のままつなぐ。略語もありとする。
マクロ定義
- すべて大文字。単語と単語のつなぎはアンダースコアを入れる。
- 定数定義、マクロ関数定義は使わない(型非安全)
変数名
グローバル変数
- 使用しないので命名規則は存在しない(半分冗談、半分本気)。
- どうしても使用するときは、・・・ うーむ、やっぱり使わないことが一番(せめてシングルトン・パターンを適用する)
データ・メンバ(メンバ変数)
- 先頭を小文字とするキャメル・タイプ。末尾にアンダースコアでデータ・メンバであることを示す。
→m_と接頭辞を付ける流儀は変数の名前より接頭辞が目立つため、意味の可読性が劣化する。末尾のアンダースコアはより目立たずにデータ・メンバであることを主張するので良しとする。なお、先頭のアンダースコアは言語仕様で使用禁止。
- 不変な変数(const)は、定数の命名に従う。(検討中)
テンプレート引数のtypename
- 大文字1文字を慣習的に使用する(例:T)
- 複数のテンプレート引数を使用するときは、T、Uのようにするが、分かりにくい場合は大文字で始まる短い単語を使用する(例:Iter)
関数の引数
- 引数名は省略せずに記述する
- 先頭を小文字とするキャメル・タイプ。
定数
- すべて大文字。単語と単語のつなぎはアンダースコアを入れる。
- 定数は積極的に活用しよう。定義するスコープは必要最小限に。あるメンバ関数内でのみ使用する定数は、ローカル変数で定義しよう。クラス内の複数メンバ関数で使用する場合、クラス静的データ・メンバで定義する。あるソースファイル内で有効な定数は無名名前空間に定義する。他のソースファイルから利用する定数は名前空間に定義する。
ハンガリアン記法は使わない
- 型情報を変数名に組み込むハンガリアンは型安全な言語では百害あって一利なし
- ジェネリックス(テンプレート)・プログラミングでの型情報ハンガリアンって???
- アプリケーション・ハンガリアンの必要性は、変数の命名が適切でないか変数のスコープが広すぎて類似変数がいくつも登場するのが原因と考える。適切な命名と極小なスコープで変数を使用するのがまず第一。
命名規則一覧表
命名項目 |
お薦め規則 |
|
|
名前空間 |
すべて小文字。単語間連結は直接。 |
|
|
ユーザ定義型
class, struct, enum, typedef |
アッパーキャメルケース |
|
|
メンバ関数名 |
インスタンス |
ローワキャメルケース |
|
|
クラス(static) |
|
|
|
メンバ変数名 |
インスタンス |
ローワキャメルケースで末尾にアンダースコアを付加 |
|
|
クラス(static) |
ローワキャメルケースで末尾にアンダースコア2連を付加 |
|
|
引数名 |
|
ローワキャメルケース |
|
|
ローカル変数名 |
|
ローワキャメルケース |
|
|
グローバル関数名 |
|
|
|
|
グローバル変数名 |
|
|
|
|
ファイルスコープ関数名 |
|
ローワキャメルケース |
|
|
ファイルスコープ変数名 |
|
ローワキャメルケース |
|
|
マクロ定義名 |
|
すべて大文字で単語間連結はアンダースコアを用いる |
|
|
- アッパーキャメルケース
- 先頭は大文字、複数単語で構成するときは続く単語の先頭1文字を大文字として直接連結。
例: AbstractButton
- ローワキャメルケース
- 先頭は小文字、複数単語で構成するときは続く単語の先頭1文字を大文字として直接連結。
例: createPushButton、currentPosition
コメント編
ファイルコメント
- 変更履歴は、ファイルのバージョンを示すもの1つのみとし、過去の履歴をコメントに残さない
バージョン管理ツールで自動展開されるキーワードを使う
コード内のコメント
- 前のバージョンのコードをコメントで残すことはしない(バージョン管理ツールを使うべし)
- 修正開始、終了といったコメントは残さない(バージョン管理ツールを使うべし)
ガイドライン編
クラスの定義
クラス定義の雛型
class Simple {
int value;
explicit Simple(const Simple& rhs);
void operator=(const Simple& rhs);
public:
Simple();
~Simple();
};
コピーコンストラクタと代入演算子はコンパイラの自動生成が適用されないように明示的に定義する。ただし、クラスの設計上コピーコンストラクタと代入演算子を提供すると決定するまではprivateとしておく。
引数が1つのコンストラクタは、explicit宣言して暗黙的に変換されるのを抑制する。
Simple::Simple() : value(0) {
}
Simple::Simple(const Simple& rhs) {
value = rhs.value;
}
Simple::~Simple() {
}
void Simple::operator=(const Simple& rhs) {
if (this == &rhs) {
return;
}
value = rhs.value;
}
定数・値の定義
定数は、使用される範囲(スコープ)と用途に応じて定義する
定数は、定数が使用される範囲のもっとも小さい(局所的な)スコープに定義する。
定数の使用注意事項
#defineで定数を定義して使用しない。defineマクロは、型安全性とnamespace等のスコープ管理を破壊するため。
classの静的メンバ変数のconstによる定数定義を安易に使用しない。protected/privateな場合はクラス静的メンバ変数にはしない。publicな場合は、そのクラスが提供するのがふさわしい定数に限って使用する。 |
以下に、定数を使用する局面とその場合の定数定義方法を規定する。
- 定数が一箇所でしか使用されない(唯一の使用)
定数を定義せず、即値で使用する。
- 定数が1つの(メンバ)関数内の複数個所で使用される
定数を、関数内にconst staticローカル変数として定義する
// Foo.cpp
bool isInclude(double x, double y) {
const static double ACCELERATION_DUE_TO_GRAVITY = 9.80665;
...
}
- 定数が同一ファイル(.cpp)内の複数個所で使用される
定数を実装ファイル(.cpp)の無名namespace内にconst変数として定義する
// Foo.cpp
namespace {
const double ACCELERATION_DUE_TO_GRAVITY = 9.80665;
}
bool updatePosition(double x, double y, double tick) {
double newX = x + ACCELERATION_DUE_TO_GRAVITY * tick;
...
}
- 定数が同一ファイル(.cpp)内の関数の引数・戻り値として使用される
定数以外の値が引数・戻り値として使用されることを防止する必要があるならば、enumを使用する
// Foo.cpp
namespace {
enum Suit {
CLUBS, DIAMONDS, HEARTS, SPADES
};
}
bool canAccept(const Suit& card) {
if (DIAMONDS == card) {
return true;
}
return false;
}
- 注記1)
- enum型変数へint型の値を代入しようとしてもC++ではコンパイルエラーとなりますが、enum型列挙子をint型変数へ代入することはできます。
- 注記2)
- enum型列挙子は、enum型宣言に閉じられずに外側のスコープに公開されます。上のコード例では、ファイル全体にCLUBS, DIAMONDS,
... の識別子が適用されます。ファイル内ならまだよいですが、ヘッダーファイルに定義し複数ファイルに利用される場合、注意が必要です。
- ファイル(.cpp)をまたがって定数が使用される
定数を提供する側で用意したヘッダーファイルに、const変数で定義します。このとき、必ず名前付きnamespaceの中にenum型を定義する。あるいはクラス(オブジェクト指向)設計を適用している場合、クラス内にenumを定義する。
// Foo.h
namespace geophysics {
const double ACCELERATION_DUE_TO_GRAVITY = 9.80665;
}
// Bar.cpp
...
double val = geophysics::ACCELERATION_DUE_TO_GRAVITY * weight;
注記) グローバル(namespaceスコープ)のconst変数を#includeしてコンパイルされるコードを見ると、コンパイル単位でローカルな領域に定数の値が格納され、定数のシンボルは公開されないようです。(GCC/C++
4.1.2 on Linuxで確認)
- ファイル(.cpp)をまたがって関数の引数・戻り値に使用される
定数を提供する側で用意したヘッダーファイルに、enum型const変数として定義します。このとき、必ず名前付きnamespaceの中にenum型を定義する。あるいはクラス(オブジェクト指向)設計を適用している場合、クラス内にenumを定義する。
例1) namespace内にenum型を定義したサンプルコード
// Foo.h
namespace cards {
enum Suit {
CLUBS, DIAMONDS, HEARTS, SPADES
};
}
// Bar.cpp
...
if (card.suite() == cards::SPADES) {
...
}
例2) クラス内にenum型を定義したサンプルコード
// Bar.h
class Cards {
public:
enum Suit {
CLUBS, DIAMONDS, HEARTS, SPADES
};
...
};
// Hoge.cpp
...
if (card.suite() == Cards::HEARTS) {
...
}
- 定数値と定数名称をペアで管理したい(コードと名称)
T.B.D.
- 集合として列挙を扱いたい
T.B.D.
- 再コンパイルすることなく定数値の変更を反映したい
T.B.D.
ヌルポインタ
例外
値をスローし参照でキャッチ
class Exception {};
void throwFunction {
throw Exception();
}
void doSomething() {
try {
throwFunction();
} catch (const Exception& ex) {
...
}
}