フリーソフト

« Google特有のマジック | トップページ | 命名規則 »

その他のC++の機能

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

前へ | 目次 | 次へ

その他のC++の機能

参照引数

参照渡しのパラメータにはconstを付けること。

Googleのコードでは、入力引数は値渡し、あるいはconstの参照渡しとし、出力引数はポインタ渡しとしている。入力引数はconstのポインタでもよい。

入力引数においてはconst T& よりもconst T* がよい場合がある。例えば、

  • NULLポインタを渡したい場合
  • 関数内でポインタや参照を入力引数に保存する場合

具体的な理由がない限りはconst T* ではなくconst T& を選択すること。

右辺値参照(C++11)

右辺値参照はムーブコンストラクタ、ムーブ代入オペレータを定義するためだけに使うこと。std::forward関数は使用禁止。

関数のオーバーロード

オーバーロード関数(コンストラクタを含む)は、呼び出したときに何が起こるかが理解できる場合にのみ利用すること。

オーバーロードを使う前に、関数名に引数に関する情報を付加することを検討すること。例えば、Append()ではなく、AppendString() やAppendInt() とするなど。

デフォルト引数

関数のデフォルト引数は使用禁止。代わりに関数のオーバーロードを使用すること。

例外は以下の通り。

  • 関数が .ccファイル(もしくは名前なし名前空間)内でスタティックな場合。
  • コンストラクタ。
  • 可変長引数リストをシミュレートするためにデフォルト引数を使う場合。
// 空のAlphaNum をデフォルト引数に使用し、最大で4つの引数をサポート
string StrCat(const AlphaNum &a,
              const AlphaNum &b = gEmptyAlphaNum,
              const AlphaNum &c = gEmptyAlphaNum,
              const AlphaNum &d = gEmptyAlphaNum);

可変長配列とalloca()

可変長配列やalloca() は使用禁止。代わりにstd::vectorやstd::unique_ptr<T[ ]> のような安全なアロケータを使用すること。発見困難なメモリ上書きのバグを引き起こす恐れがある。

フレンド

常識の範囲内でfriendクラスやfriend関数を使用してもよい。

フレンドは同じファイル内に定義すること。privateメンバの内容を確認するために他のファイルを探す手間が軽減できる。

フレンドはカプセル化の境界を拡大するが破壊することはない。そのため、メンバをpublicにするよりもよい場合がある(例:あるクラスにのみアクセス権を与えたい場合など)。

例外

C++の例外を使用してはならない。

GoogleのC++コードは例外を扱うようには書かれておらず、例外を生成するコードを統合することは困難である。例外の代わりにエラーコードやアサーションなどを使用すること。

このルールは、経験的な根拠に基づくものである。

C++に加えられた例外も使用禁止(oexcept、std::exception_ptr、std::nested_exception)。

Windowsでは例外がある。「Windowsのコード」を参照のこと。

実行時型情報(RTTI)

実行時型情報(RTTI)の使用を避けること。

RTTIを使用すると実行時にオブジェクトのC++クラスを問い合わせることができる(typeidやdynamic_castを使用)。しかしながら、実行時にオブジェクトの型が必要になるのは、たいていクラス階層の設計に欠陥があるということを暗示している。RTTIの乱用はコードの保守を困難にする。

クラスごとに処理を分岐したい場合は、RTTIではなく以下に示す代替方法の利用を検討すること。

  • 仮想メソッドを使ってサブクラスごとに異なる処理を実行する。オブジェクト自身に処理内容を置く方法。
  • Visitorデザインパターンのようなダブルディスパッチを使う。オブジェクトの外側に処理を置く方法。

プログラムロジック的に、与えられた基本クラスのインスタンスが特定の派生クラスのインスタンスであることが保証されている場合はdynamic_castが利用できる。そのような状況では、たいていstatic_castを使うこともできる。

キャスト

static_cast<>() のようなC++のキャストを使用すること。int y = (int)x; や int y = int(x); のようなキャスト形式は使用禁止。

  • 値を変換するCスタイルのキャストの代わりにstatic_castを使用すること。また、クラスポインタを親クラスへ明示的にキャストする場合もstatic_castを使用すること。
  • const装飾子を取り除く場合はconst_castを使用すること。
  • 整数とその他のポインタタイプの間の安全でない変換にはreinterpret_castを使用すること。変換の内容やエイリアス問題を理解している場合のみ使用すること。

dynamic_castについては「実行時型情報(RTTI)」を参照のこと。

ストリーム

ストリームはロギングのためだけに使用すること。それ以外ではprintfライクなルーチンを使用すること。

前置インクリメントと前置デクリメント

イテレータやその他テンプレートオブジェクトには、インクリメント演算子およびデクリメントオペレータを前置形式(++i)で使用すること。単純なスカラー値(オブジェクト以外)はどちらの形式でもよい。

戻り値が無視される場合、後置形式ではiのコピーが求められる。iがイテレータや非スカラー型の場合、コピーのコストが高くなるため、前置形式の方が効率的となる。

constの使用

constが意味をなすときは必ず使用すること。C++11ではconstexprを使用したほうがよい。コンパイル時の型チェックにより、早い段階でエラーが発見できるようになる。

  • 参照やポインタで渡された引数が、関数内で変更されない場合はconstとする。
  • 可能な限りメソッドはconstで宣言する。アクセッサは通常はconstである。その他のメソッドについては、データメンバを変更せず、非constメソッドを呼び出さず、データメンバへの非constポインタや非const参照を返さない場合はconstとする。
  • クラス生成後、変更の必要がないデータメンバはconstにする。

mutableキーワードを使用してもよいが、スレッドでの利用は安全ではない。最初にスレッドの安全性を慎重に考慮すること。

constexprの使用(C++11)

C++11ではconstexprを使用して定数を定義し、初期化すること。

整数型

組み込みのC++整数型のうち、使用してもよいのはintのみ。異なるサイズの変数が必要な場合は、

にあるint16_tのような固定幅の整数型を使用すること。変数の値が2^31(2GiB)以上になりうる場合は、int64_tのような64ビット型を使用すること。計算過程で、より大きい型が要求される場合があるため、疑わしい場合はより大きい型を選択すること。

C++における整数型はコンパイラや環境によってサイズが変化する。

にはint16_t、uint32_t、int64_tなどの型が定義されている。整数のサイズに保証が必要なときは、shortやunsigned long longではなく、常にこれらの型を使用すること。size_tやptrdiff_tのような標準型を使用してもよい。

intが32ビットより大きいと仮定してはならない。64ビットの整数型が必要な場合は、int64_tやuint64_tを使用すること。数値が大きくなりそうな場合もint64_tを使用すること。

正当な理由がない限り、uint32_tのようなunsigned整数型を使用してはならない。数値が負にならないことを示す(自分自身へのドキュメント)ためだけにunsigned型を使用してはならない。代わりにアサーションを使用すること。

整数型の変換に注意すること。整数変換と整数拡張は非直感的なふるまいを引き起こすことがある。

64ビットの移植性

64ビットでも32ビットでも動作するコードを書くべきである。print、比較、構造体のアラインメント問題に注意すること。

  • printf() 指定子は32ビットと64ビットシステムで互換性がないものがある。C99では互換形式の指定子がいくつか定義されているが、MSVC 7.1では認識されないものがある。その場合は、標準インクルードファイルinttypes.hに独自の定義を記述しなくてはならないことがある。
    // printf macros for size_t, in the style of inttypes.h
    #ifdef _LP64
    #define __PRIS_PREFIX "z"
    #else
    #define __PRIS_PREFIX
    #endif
    
    // Use these macros after a % in a printf format string
    // to get correct 32/64 bit behavior, like this:
    // size_t size = records.size();
    // printf("%"PRIuS"\n", size);
    
    #define PRIdS __PRIS_PREFIX "d"
    #define PRIxS __PRIS_PREFIX "x"
    #define PRIuS __PRIS_PREFIX "u"
    #define PRIXS __PRIS_PREFIX "X"
    #define PRIoS __PRIS_PREFIX "o"
    
    使用禁止 こちらを使用 備考
    void * %lx %p
    int64_t %qd, %lld %"PRId64"
    uint64_t %qu, %llu, %llx %"PRIu64", %"PRIx64"
    size_t %u %"PRIuS", %"PRIxS" C99では%zu
    ptrdiff_t %d %"PRIdS" C99では%td
    PRI* マクロは独立の文字列に展開されることに注意。非定数のフォーマット文字列を使用する場合は、名前ではなくマクロの値をフォーマットに挿入する必要がある。指定子の長さなどを%の後に含めることもできる。例えば、printf("x = %30"PRIuS"\n", x) は32ビットLinuxではprintf("x = %30" "u" "\n", x) に展開され、コンパイラはprintf("x = %30u\n", x) として扱う。
  • sizeof(void *) != sizeof(int) であることに注意。ポインタサイズの整数が必要な場合はintptr_tを使用すること。
  • ディスクに保存されている構造体のアラインメントに注意すること。int64_tやuint64_tのメンバを持つクラス/構造体は、64ビットシステムではデフォルトで8バイトアラインとなる。このような構造体を32ビットと64ビットシステムの間でディスク上に共有する場合、同じようにパックしなくてはならない。ほとんどのコンパイラでは構造体のアラインメントを変更する方法を提供しており、gccでは __attribute__((packed)) を使用する。MSVCでは #pragma pack() と__declspec(align()) を使用する。
  • 64ビット定数を作成するときは、LLまたはULL接尾辞を使用すること。
    int64_t my_value = 0x123456789LL;
    uint64_t my_mask = 3ULL << 48;
  • 32ビットと64ビットシステムで異なるコードが必要な場合は、#ifdef _LP64を使用すること。このような処理は可能な限り避け、変更を局所的にとどめること。

プリプロセッサのマクロ

マクロは十分に注意して使用すること。マクロよりもインライン関数やenum、const変数を使った方がよい。

コードをインライン化するマクロの代わりには、インライン関数が使用できる。定数を格納するマクロの代わりには、const変数が使用できる。長い変数名を省略するためのマクロの代わりには、参照が使用できる。マクロを使用しない方法を事前によく検討すること。

マクロを使用する場合はできるだけ以下のルールに従うこと。マクロに起因する多くの問題を避けることができる。

  • .hファイルにマクロを定義してはならない。
  • 使用する直前にマクロを #defineし、直後に #undefすること。
  • 既存のマクロを #undefして独自のマクロに置換してはならない。代わりに、ユニークな名前を付けること。
  • C++の構造を不安定に拡大するようなマクロを使用してはならない。少なくともその振る舞いをきちんと文書化すること。
  • ## を使用して、関数名、クラス名、変数名を生成しない方がよい。

0とnullptr/NULL

整数には0を、実数には0.0を、ポインタにはnullptr(もしくはNULL)を、文字には’\0’を使用すること。

sizeof

sizeof(型) ではなくsizeof(変数名) を使用した方がよい。

変数の型が変更されても、sizeof(変数名) は適切に更新される。特定の変数に関連しないコードではsizeof(型) を使用してもよい。

auto(C++11)

局所変数には型名ではなくautoを使用すること。可読性が高められる場合は通常の型宣言を使用してもよい。

局所変数以外(ファイル変数、名前空間変数、メンバ変数など)にautoを使用してはならない。auto型の変数を中括弧の初期化リストで初期化してはならない。

中括弧の初期化リスト

中括弧の初期化リストを使用してもよい。

C++03では、配列や構造体(コンストラクタなし)を中括弧の初期化リストで初期化することができた。

struct Point { int x; int y; };
Point p = {1, 2};

C++11では、この構文が一般化され、すべてのオブジェクト型が中括弧の初期化リストで作成できるようになった。

// Vectorは要素のリストをとる
vector<string> v{"foo", "bar"};

// 細かい違いはあるが基本的に同じ
vector<string> v = {"foo", "bar"};

// ‘new’ に使えるやり方
auto p = new vector<string>{"foo", "bar"};

// mapはペアのリストをとる
map<int, string> m = {{1, "one"}, {2, "2"}};

// 返り型に暗黙的に変換される
vector<int> test_function() { return {1, 2, 3}; }

// リストの要素を順に処理
for (int i : {-1, -2, -3}) {}

// 関数の呼び出し
void TestFunction2(vector<int> v) {}
TestFunction2({1, 2, 3});

autoのローカル変数に中括弧の初期化リストを代入してはならない。要素が1つの場合に混乱が起きる。

auto d = {1.23};  // d は std::initializer_list<double>型

auto d = double{1.23};  // Good -- d は double型

書式については「中括弧初期化リストの書式」を参照のこと。

ラムダ式

適切な場所でラムダ式を使用すること。デフォルトのラムダキャプチャは使用禁止。すべてのキャプチャを明示的に記述すること。

  • デフォルトのキャプチャは使用禁止。例えば、[=](int x) { return x + n; } ではなく[n](int x) { return x + n; } と書くこと。これにより、読み手はnがキャプチャされることがすぐに理解できる。
  • 名前なしラムダを短くキープしておくこと。ラムダのボディが5行を超えるようなら、ラムダに名前をつけるか、ラムダの代わりに名前付き関数を使用したほうがよい。
  • ラムダの返り型を明示的に指定したほうが読み手にとって有用な場合はそうすること。

書式については「書式-ラムダ式」を参照のこと。

テンプレート・メタプログラミング

複雑なテンプレート・プログラミングを避けること。

チームの平均的なメンバがそのコードを十分に理解しメンテナンスできるか、エラーメッセージの意味が理解できるか、呼び出す関数のフローをトレースできるか、を考えること。再帰的なテンプレートのインスタンス化や、型リスト、式テンプレート、関数オーバーロード検出のためのSFINAEやsizeofトリックなどは行き過ぎの恐れがある。

テンプレート・メタプログラミングを使用する場合は、複雑さを最小化するために相当な努力が必要になると考えなくてはならない。できる限りメタプログラミングを隠ぺいし、ヘッダを読みやすくすること。トリッキーなコードには確実にコメントを書くこと。ユーザがミスしたとき、コンパイラが発するエラーメッセージにも注意を払わなくてはならない。エラーメッセージが理解可能なものとなるよう、必要に応じてコードを調整すること。

Boost

Boostは許可されたライブラリのみ使用すること。

現在、許可されているものを示す。

機能 ヘッダ
Call Traits boost/call_traits.hpp
Compressed Pair boost/compressed_pair.hpp
Boost Graph Library (BGL) boost/graph
*ただし、以下を除く
adj_list_serialize.hpp(シリアル化)
boost/graph/parallel/*(並列アルゴリズム)
boost/graph/distributed/*(分散アルゴリズム)
プロパティマップ boost/property_map
*ただし、以下を除く
boost/property_map/parallel/*(並列プロパティマップ)
イテレータの一部 boost/iterator/iterator_adaptor.hpp
boost/iterator/iterator_facade.hpp
boost/function_output_iterator.hpp
Polygonの一部 boost/polygon/voronoi_builder.hpp
boost/polygon/voronoi_diagram.hpp
boost/polygon/voronoi_geometry_type.hpp
ビットマップ boost/bimap
統計分布と統計関数 boost/math/distributions
Multi-index boost/multi_index
ヒープ boost/heap
コンテナ boost/container/flat_map
boost/container/flat_set

他のライブラリの追加も積極的に検討している。将来、リストが拡張されるかもしれない。

以下のライブラリも許可されているが、C++11の標準ライブラリに取って代わられたため推奨されない。

  • boost/array.hppのArray:代わりにstd::arrayを使用すること。
  • boost/ptr_containerのPointer Container:代わりにstd::unique_ptrのコンテナを使用すること。

C++11

C++11(C++0xとして知られていたもの)のライブラリと言語拡張は適切に使用すること。C++11の機能をプロジェクトで使用する前に、他の環境への移植性を考慮すること。

以下のC++11の機能は使用してはならない。

  • 返り値の型が後ろに続く関数(ラムダ関数以外)。例えば、int foo() の代わりにauto foo() -> int と書くもの。既存の関数宣言とスタイルの一貫性を維持するため。
  • コンパイル時有理数(<ratio>)。テンプレートヘビーなインターフェーススタイルに関係しているという懸念のため。
  • <cfenv>と<fenv.h>ヘッダ。多くのコンパイラがこれらの機能をサポートしていないため。

  • デフォルトのラムダキャプチャ。

前へ | 目次 | 次へ

« Google特有のマジック | トップページ | 命名規則 »

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