フリーソフト

« コメント | トップページ | ルールの例外 »

書式

このドキュメントは、Google C++ Style Guideを翻訳、要約したものです。省略した部分や意訳した部分が多くあります。原文の意図が伝わるよう注意しましたが、誤りが含まれているかもしれません。正確な情報については、必ず原文を参照してください。

前へ | 目次 | 次へ

書式

コーディングのスタイルは好みによるところが大きいが、全員が同じスタイルに従うことで、誰が書いたコードでも簡単に理解することが可能となる。

以下からコードを書式化するためのemacs用の設定ファイルが入手できる。
http://google-styleguide.googlecode.com/svn/trunk/google-c-style.el

 

行の長さ

テキストの各行は最大で80文字とすること。

例外:

  • コメント行に書いたコマンド例やURLリテラルが80文字を超える場合。カット&ペーストが簡単にできるように途中で改行しなくてもよい。
  • raw-stringリテラルが80文字を超える場合。テストコードを除き、このようなリテラルはファイルの先頭付近に置くこと。
  • #include文が長いパスを持つ場合。
  • ヘッダガードが80文字を超える場合。

非ASCII文字

非ASCII文字は基本的に使用してはならない。使用する場合はUTF-8形式を使用すること。

ユーザに表示するテキストをハードコードしてはならない(英語でもだめ)。コードに非ASCII文字が含まれることはめったにないはずである。非ASCII文字をハードコードせざるを得ない場合(例えば、外部のデータファイルを読み込むコードで、非ASCIIの区切り文字をハードコードする場合など)は、多くのツールが認識できるUTF-8を使うこと。

16進数エンコードを使用してもよい。特に読みやすさが向上するような場合に推奨される。例えば、"\xEF\xBB\xBF"(もっとシンプルにu8"\uFEFF")はUnicodeの長さ0、改行なしの空白文字を表すが、UTF8としてソースに含まれていたら目視できない。

エスケープシーケンス\uXXXXを含む文字列リテラルがUTF-8でエンコードされていることを保証するには、u8接頭辞を使用すること。UTF-8でエンコードされた非ASCII文字を含む文字列には使用しないこと。これは、コンパイラがソースファイルをUTF-8として解釈できない場合に、不正な出力を生成するため。

C+11のchar16_tとchar32_t文字型は使用してはならない。これらは非UTF-8テキストのためのものではない。同じ理由でwchar_tを使うべきでない(wchar_tを広範囲にわたって使用するWindows APIとやりとりするコードは除く)

スペース vs. タブ

インデントはスペース2つとすること。タブは使用しないこと。

関数宣言と定義

関数名と同じ行に戻り型を書くこと。1行に収まる場合は同じ行にパラメータも書くこと。

ReturnType ClassName::FunctionName(Type name1, Type name2) {
  DoSomething();
  ...
}
// 1行に収まらない場合
ReturnType ClassName:: FunctionName(Type name1, Type name2,
                                    Type par_name3) {
  ...
}
// 1つ目のパラメータから収まらない場合
ReturnType LongClassName::ReallyReallyLongFunctionName(
    Type par_name1,  // インデントはスペース4つ
    Type par_name2,
    Type par_name3) {
  DoSomething();  // インデントはスペース2つ
}

注意すべきポイント

  • 戻り型と関数名が1行に収まらない場合は、それらの間で改行すること。
  • 戻り型の後ろで改行した場合は、インデントしないこと。
  • 開き丸かっこは常に関数名と同じ行に書くこと。
  • 関数名と開き丸かっこの間にはスペースを書かないこと。
  • 丸かっことパラメータの間にスペースを書かないこと。
  • 開き中括弧は最後のパラメータと同じ行に書くこと。
  • 閉じ中括弧は関数の最後の行に書くか、開き中括弧と同じ行に書くこと(このスタイルが許可されている場合)。
  • 閉じ丸括弧と開き中括弧の間にはスペースを書くこと。
  • すべてのパラメータには名前を付け、宣言と実装において同じ名前とすること。
  • 可能なら、すべてのパラメータを整列させること。
  • デフォルトのインデントはスペース2つ。
  • 括られたパラメータのインデントはスペース4つ。

使用していないパラメータがある場合は、関数宣言において変数名をコメントアウトする。変数名を消してしまうと、後で変数の意味が分からなくなってしまう。

void Circle::Rotate(double /*radians*/) {}

ラムダ式

ラムダ式のパラメータや本体は他の関数と同じような形式で書くこと。キャプチャリストは他のカンマ区切りリストと同じように書くこと。

参照キャプチャについては、アンパサンド(&)と変数名の間にスペースを入れないこと。

int x = 0;
auto add_to_x = [&x](int n) { x += n; };

短いラムダは、関数の引数にインラインで記述してもよい。

関数呼び出し

関数呼び出しは、1行で書くこと。

1行に収まらない場合は、引数の途中で改行し、インデントを最初の引数に合わせる。開き丸括弧 ‘(‘ の後ろや、閉じ丸括弧 ‘)’ の前にスペースを入れてはならない。開き丸括弧 ‘(‘ の後ろで改行し、引数を別の行から書き始めてもよい(インデントはスペース4つ)。いずれの場合も、特別な理由がない限り、複数の引数を同じ行に置くなどして行数を最小にとどめること。

bool retval = DoSomething(averyveryveryverylongargument1,
                          argument2, argument3);

DoSomething(
    argument1, argument2,  // 4 space indent
    argument3, argument4);

中括弧初期化リストの書式

関数呼び出しの書式と同じ。

名前(型名や変数名)の後ろにリストが続く場合は、{ } を関数呼び出しの丸括弧と見なしてフォーマットする。名前がない場合は、名前の長さが0と仮定する。

// 1行で書く例
return {foo, bar};
functioncall({foo, bar});
pair

p{foo, bar}; // 複数行で書く例 SomeFunction(     {"assume a zero-length name before {"},     some_other_function_parameter); SomeType variable{     "This is too long to fit all in one line"}; MyType m = {  // { の前で改行してもよい     superlongvariablename1,     {short, interior, list},     {interiorwrappinglist,     interiorwrappinglist2}};

条件文

条件文の丸括弧内にはスペースを書かないことを推奨。ifとelseキーワードは別の行に書くこと。

条件文の書式は2種類。丸括弧の内側にスペースを入れるものと、入れないもの。

if (condition) {  // 丸括弧内にスペースなし
  ...  // インデントはスペース2つ
} else if (...) {  // 閉じ中括弧と同じ行にelse
  ...
} else {
  ...
}
if ( condition ) {  // 丸括弧内にスペースあり – まれな書き方
  ...
}

どちらの書式でもよいが一貫性をもつこと。既存ファイルを修正する場合は、すでに使用されている書式に従うこと。また、新しいコードを書く場合は、プロジェクト内の他のファイルの書式を確認し、それに従うこと。判別がつかず、かつ個人的な好みがない場合は、スペースなしの書式にすること。

if と ‘(‘ の間にスペースを入れること。 ‘)’ と ‘{‘ の間にもスペースを入れること。

if(condition) {   // だめ - IFの後にスペースがない
if (condition){   // だめ - { の前にスペースがない
if(condition){    // だめだめ

読みやすさのため短い条件文を1行に書いてもよい。ただしelse節がある場合は除く。

if (x == kFoo) return new Foo();
if (x == kBar) return new Bar();

// だめ - ELSE節があるのにIF文が1行になっている
if (x) DoThis();
else DoThat();

構文上、文が1つの場合には中括弧は不要であるが、好みに応じて中括弧を書いてもよい。常に中括弧を書くというスタイルのプロジェクトもある。

if-else文で中括弧を使用する場合は、他の部分でも使わなくてはならない。

// だめ - IFには中括弧があるがELSEにはない
if (condition) {
  foo;
} else
  bar;

// だめ - ELSEに中括弧があるがIFにはない
if (condition)
  foo;
else {
  bar;
}

ループとスイッチ文

switch文のブロックに中括弧を使用してもよい。非自明なcaseの通り抜け(breakなしのcase)にはコメントをつけること。ループのボディが1文の場合、中括弧は任意。空のループのボディは {} かcontinueとすること。

switch文のcaseブロックに中括弧を書く場合は、以下のように書くこと。

switch (var) {
  case 0: {  // スペース2つでインデント
    ...      // スペース4つでインデント
    break;
  }
  ...
  default: {
    assert(false);
  }
}

switch文の変数が列挙値でない場合はdefault caseを必ず書くこと(列挙値の場合は未処理の値があるとコンパイラが警告を発してくれる)。default caseが実行されないはずの場合は、単純にassertを置くこと。

空ループの本体には{}あるいはcontinueを使用すること。セミコロンのみは禁止。

while (condition) {
  // falseを返すまでテストを継続
}
for (int i = 0; i < kSomeNumber; ++i) {}  // 空のボディ
while (condition) continue;  // continue
// だめ – do/whileループの一部に見える
while (condition);  

ポインタと参照表現

ピリオドや矢印の前後にスペースを入れてはならない。ポインタオペレータ(*, &)の後ろにスペースを入れてはならない。

x = *p;
p = &x;
x = r.y;
x = r->y;

ポインタ変数やポインタ引数の宣言では、型名か変数名のどちらかにポインタオペレータを隣接させる。

// OK - 変数名にポインタオペレータが隣接
char *c;
const string &str;

// OK - 型名にポインタオペレータが隣接
char* c;  // 複数の場合は "char* c, *d, *e, ...;" とする!
const string& str;
char * c;  // だめ - * の両側にスペース
const string & str;  // だめ - & の両側にスペース

ファイル内で一貫性を持つこと。既存ファイルを修正する場合は、既存のコードのスタイルに合わせること。

論理式

論理式が1行に収まらない場合は、一貫性を持って行を分割すること。

Googleのコードでは論理オペレータを行末に置くスタイルが一般的。

if (this_one_thing > this_other_thing &&
     a_third_thing == a_fourth_thing &&
     yet_another && last_one) {
  ...
}

オペレータを行の始めに置いてもよい。可読性を高めるために丸括弧を追加してもよい。and や cmplのような単語オペレータは使用せず、常に && や ~ のような記号オペレータを使用すること。

戻り値

必要以上にreturn式を丸括弧で囲んではならない。

// 複雑な式を読みやすくするための丸括弧はOK
return (some_long_condition && another_condition);
// だめな例
return (value);  // var = (value); とは書かないよね
return(result);  // return は関数ではない!

変数と配列の初期化

初期化には = あるいは ( )、{ } が使用できる。

// 全部OK
int x = 3;
int x(3);
int x{3};
string name = "Some Name";
string name("Some Name");
string name{"Some Name"};

中括弧の初期化リストをstd::initializer_listで使用するときは注意。

vector

v(100, 1);  // 100個の1を持つベクトル vector

v{100, 1};  // 100と1を持つベクトル

プリプロセッサ命令

プリプロセッサ命令のシャープ記号は、常に行の先頭に書くこと。

  if (lopsided_score) {
#if DISASTER_PENDING  // 正しい
    DropEverything();
# if NOTIFY  //正しい、ただし#の後ろのスペースは必須ではない
    NotifyClient();
# endif
#endif
    BackToNormal();
  }
// だめ - シャープ記号がインデントされている
  if (lopsided_score) {
    #if DISASTER_PENDING  // だめ!
    DropEverything();
    #endif                // だめ!
    BackToNormal();
  }

クラスの書式

セクションはpublic、protected、private順に書き、各キーワードはスペース1つでインデントすること。

クラス宣言の基本的な書式(コメントは省略)は以下の通り。

class MyClass : public OtherClass {
 public:      // 注意:スペース1つでインデント!
  MyClass();  // スペース2つでインデント
  explicit MyClass(int var);
  ~MyClass() {}

  void SomeFunction();
  void SomeFunctionThatDoesNothing() {
  }

  void set_some_var(int var) { some_var_ = var; }
  int some_var() const { return some_var_; }

 private:
  bool SomeInternalFunction();

  int some_var_;
  int some_other_var_;
};

注意点:

  • 基本クラス名はサブクラス名と同じ行に書く(80文字に収まる場合)。
  • public:、protected:、private:キーワードはスペース1つでインデント。
  • これらのキーワードの前は空行にする(最初のキーワードは除く)。このルールは小さなクラスでは任意。
  • キーワードの後ろは空行にしないこと。
  • セクションは、public、protected、privateの順に書く。
  • 各セクション内での宣言順序については「宣言の順序」を参照のこと。

コンストラクタ初期化子リスト

コンストラクタの初期化子リストの書き方は次の2種類。

// 1行に収まるとき
MyClass::MyClass(int var) : some_var_(var) {}

もしくは

// 複数行が必要なとき
// スペース4つでインデント、1番目の初期化子の行にコロン
MyClass::MyClass(int var)
    : some_var_(var),  // スペース4つでインデント
      some_other_var_(var + 1) {
  ...
}

名前空間の書式

名前空間のコンテンツはインデントしてはならない。

namespace {
  // 間違い、インデントしてはならない
  void foo() {
    ...
  }
}  // namespace

ネストされた名前空間を宣言する場合は、それぞれを1行に書くこと。

namespace foo {
namespace bar {

水平方向の空白

以下のルールに従って水平方向の空白を使用すること。行末に空白を入れてはならない。

一般

void f(bool b) {  // 開き中括弧 ‘{‘ の前にスペースを入れる
  ...
int i = 0;  // セミコロンの前はスペース不要
// 初期化リストの中括弧内のスペースは任意
// 入れる場合は両側に入れること!
int x[] = { 0 };
int x[] = {0};
// 継承と初期化リストのコロンの両側にはスペースを入れる
class Foo : public Bar {
 public:
  Foo(int b) : Bar(), baz_(b) {}  // 空の中括弧内はスペース不要
  void Reset() { baz_ = 0; }  // 中括弧と実装の間にスペース
  ...

ループと条件式

if (b) {  // 条件式・ループのキーワードの後ろにスペースを入れる
} else {  // elseの前後にスペースを入れる
}
while (test) {}  // 丸括弧内は通常スペース不要
switch (i) {
for (int i = 0; i < 5; ++i) {

// 丸括弧内にスペースを入れてもよいが、まれな書き方
switch ( i ) {
if ( test ) {
for ( int i = 0; i < 5; ++i ) {

// forループではセミコロンの後ろにスペースを入れる
// セミコロンの前にスペースを入れてもよいが、まれな書き方
for ( ; i < 5 ; ++i) {

// 範囲ベースのforループではコロンの前後にスペースを入れる
for (auto x : counts) {

switch (i) {
  case 1:         // コロンの前はスペース不要
    ...
  case 2: break;  // コードがある場合はコロンの後ろにスペース

演算子

// 代入演算子の前後にはスペースを入れる
x = 0;

// 2項演算子の前後にはスペース、係数のスペースはなくてもOK
// 丸括弧とコンテンツの間はスペース不要
v = w * x + y / z;
v = w*x + y/z;
v = w * (x + z);

// 単項演算子と引数の間はスペース不要
x = -5;
++x;
if (x && !y)

テンプレートとキャスト

// 角括弧の内側にはスペース不要
// キャストの ‘<‘ の前や、’>(‘ の間にはスペース不要
vector

x; y = static_cast

(x); // 型名とポインタの間のスペースはOK、一貫性を持つこと vector

x; set

> x;  // C++11では許される書き方 set

> x; // C++03では ‘> >‘ にスペースが必要 // 対称に ‘< <‘ にスペースを入れてもよい set< list

> x;

垂直方向の空白

垂直方向の空白(空行)は最小限にすること。

これはルールというよりも原則である。具体的には、関数と関数の間に2もしくは3行以上の空行を入れてはならない。関数の先頭や最後を空行にしてはならない。関数内での空行の使用も制限しなくてはならない。

1画面に多くのコードを表示するとプログラムの流れが理解しやすくなる。もちろん、詰まり過ぎたコードは散らばりすぎたコードと同じくらい読みにくい。うまく調整すること。

空行が有用かどうか判断する際の経験則

  • 関数の先頭や最後にある空行が可読性を高めることはない
  • 一連のif-elseブロック内の空行は可読性を高める

前へ | 目次 | 次へ

« コメント | トップページ | ルールの例外 »

技術文書」カテゴリの記事