C++ スタイルガイド
一般的な推奨事項
以下は推奨事項であり、必須要件ではありません。
コードを編集する場合、既存のコードのフォーマットに従うことが理にかなっています。
コードスタイルは一貫性が必要です。一貫性はコードの可読性を高め、コードの検索を容易にします。
多くのルールには論理的な理由がない場合があります。これらは確立された慣行に基づいています。
フォーマット
1. ほとんどのフォーマットは clang-format
によって自動的に行われます。
2. インデントは 4 スペースです。タブを押すと 4 スペースが追加されるように開発環境を設定してください。
3. 開き波括弧と閉じ波括弧は別の行に配置する必要があります。
4. 関数本体が 1 つの statement
の場合、それを 1 行に配置できます。波括弧の周りにはスペースを置きます (行の終わりのスペースを除く)。
5. 関数の場合。括弧の周りにスペースを置かないでください。
6. if
、for
、while
およびその他の式では、開き括弧の前にスペースを挿入します (関数呼び出しとは対照的に)。
7. 二項演算子 (+
, -
, *
, /
, %
, ...) と三項演算子 ?:
の周りにはスペースを追加します。
8. 行の改行が入る場合、演算子を新しい行に移動し、その前のインデントを増やします。
9. 必要に応じて、行内での整列のためにスペースを使用できます。
10. 演算子 .
と ->
の周りにはスペースを使用しないでください。
必要に応じて、演算子を次の行に折り返すことができます。この場合、その前のオフセットを増やしてください。
11. 単項演算子 (--
, ++
, *
, &
, ...) と引数の間にスペースを置かないでください。
12. コンマの後にはスペースを置きますが、前には置きません。同じルールは for
式内のセミコロンにも当てはまります。
13. []
演算子の周りにスペースを使用しないでください。
14. template <...>
式では、template
と <
の間にスペースを置き、<
の後や >
の前にスペースを置かないでください。
15. クラスおよび構造体では、public
、private
、および protected
を class/struct
と同じレベルに書き、残りのコードはインデントしてください。
16. 同じ namespace
がファイル全体で使用されている場合、他に重要なものが無ければ、namespace
内でのオフセットは必要ありません。
17. if
、for
、while
、または他の式のブロックが単一の statement
から構成されている場合、波括弧は省略可能です。代わりに statement
を別の行に置いてください。このルールはネストされた if
、for
、while
にも有効です。
しかし、内部の statement
が波括弧や else
を含む場合、外部ブロックは波括弧で書く必要があります。
18. 行の末尾にスペースがあってはいけません。
19. ソースファイルは UTF-8 でエンコードされています。
20. 文字列リテラルに非 ASCII 文字を使用することができます。
21. 複数の式を 1 行に書かないでください。
22. 関数内のコードのセクションをグループ化し、それらを 1 行または 2 行の空白で分けてください。
23. 関数、クラスなどを 1 行または 2 行の空白で分けてください。
24. 値に関連する A const
は型名の前に書かなければなりません。
25. ポインタや参照を宣言する場合、*
および &
記号は両側にスペースを入れて分離するべきです。
26. テンプレート型を使用する場合、(最も簡単な場合を除き)using
キーワードでエイリアスを作成します。
言い換えれば、テンプレートパラメータは using
のみで指定され、コード内で繰り返されません。
using
は、関数内などでローカルに宣言できます。
27. 異なる型の複数の変数を 1 つのステートメントで宣言しないでください。
28. C スタイルのキャストを使わないでください。
29. クラスや構造体では、メンバーと関数をそれぞれ各可視性スコープ内でグループ化してください。
30. 小さなクラスや構造体では、メソッド宣言と実装を分ける必要はありません。
これは、任意のクラスや構造体内の小さなメソッドにも当てはまります。
テンプレートクラスや構造体の場合は、メソッド宣言と実装を分けないでください(そうしないと、同じ翻訳単位内で定義する必要があります)。
31. 140 文字で行を折り返すことができます。80 文字で折り返す必要はありません。
32. 後置インクリメント/デクリメント演算子が必要でなければ、常に前置のものを使用してください。
コメント
1. コードのすべての非自明な部分にコメントを追加することを忘れないでください。
これは非常に重要です。コメントを書くことで、そのコードが不要であることや、設計が間違っていることに気づく場合があります。
2. コメントは必要に応じて詳細にすることができます。
3. 説明するコードの前にコメントを配置してください。稀に、コメントはコードの後、同じ行に配置することができます。
4. コメントは英語で書く必要があります。
5. ライブラリを記述している場合は、メインヘッダーファイルにそれを説明する詳細なコメントを含めてください。
6. 追加情報を提供しないコメントを追加しないでください。特に、次のような空のコメントを残さないでください。
例は http://home.tamk.fi/~jaalto/course/coding-style/doc/unmaintainable-code/ から借用されたものです。
7. 各ファイルの最初に、著者や作成日などのガラクタコメントを記述しないでください。
8. 単一行コメントは ///
で始まり、複数行コメントは /**
で始まります。これらのコメントは「ドキュメント」と見なされます。
注意: これらのコメントからドキュメントを生成するために Doxygen を使用できます。しかし、Doxygen は一般的には使用されません。コーディング時のナビゲートは IDE で行う方が便利だからです。
9. 複数行コメントは、開始と終了に空行があってはいけません(複数行コメントを閉じる行を除いて)。
10. コードをコメントアウトする場合は、基本的なコメントを使用してください。「ドキュメント化」コメントは避けてください。
11. コードのコメントアウトされた部分は、コミットする前に削除してください。
12. コメントやコードに不適切な言葉を使用しないでください。
13. 大文字は使用しないでください。過度の句読点も避けてください。
14. コメントを区切りとして使用しないでください。
15. コメント内で議論を始める必要はありません。
16. ブロックの最後でその内容を説明するコメントを書く必要はありません。
名前
1. 変数およびクラスメンバーの名前には小文字の英字とアンダースコアを使用します。
2. 関数(メソッド)の名前には小文字から始まるキャメルケースを使用します。
3. クラス(構造体)の名前には大文字から始まるキャメルケースを使用します。インターフェースには I 以外のプレフィックスを使用しません。
4. using
はクラスと同じ方式で名前を付けます。
5. テンプレート型引数の名前: 簡単な場合は T
を使用します; T
、U
; T1
、T2
.
より複雑な場合は、クラス名のルールに従うか、T
プレフィックスを追加してください。
6. テンプレート定数引数の名前: 変数名のルールを従うか、簡単な場合は N
を使用します。
7. 抽象クラス(インターフェース)には I
プレフィックスを追加できます。
8. 変数をローカルに使用する場合、短い名前を使用できます。
それ以外のすべての場合は、意味を説明する名前を使用してください。
9. define
とグローバル定数の名前は、アンダースコアを使用してすべて大文字にします。
10. ファイル名はその内容と同じスタイルを使用するべきです。
ファイルが単一のクラスを含む場合、ファイルはそのクラスと同じ名前にします(キャメルケース)。
ファイルが単一の関数を含む場合、ファイルはその関数と同じ名前にします(キャメルケース)。
11. 名前に省略形が含まれている場合:
- 変数名の場合、省略形は小文字を使用するべきです
mysql_connection
(mySQL_connection
ではなく)。 - クラスや関数の名前の場合、省略形の大文字を保持します
MySQLConnection
(MySqlConnection
ではなく)。
12. クラスメンバーを初期化するためだけに使用されるコンストラクタ引数は、クラスメンバーと同じ名前を付けるべきですが、末尾にアンダースコアを付けます。
アンダースコアのサフィックスは、引数がコンストラクタ本体で使用されない場合は省略できます。
13. ローカル変数とクラスメンバーの名前には違いがありません(プレフィックスは必要ありません)。
14. enum
の定数には大文字でキャメルケースを使用します。すべての大文字も許可されます。enum
が非ローカルな場合、enum class
を使用します。
15. すべての名前は英語でなければなりません。ヘブライ語の言葉の音訳は許可されていません。
not T_PAAMAYIM_NEKUDOTAYIM
16. よく知られている省略形は許可されます(意味を簡単に検索エンジンや Wikipedia で確認できる場合)。
AST
、SQL
。
NVDH
(ランダムな文字)はノー。
未完成の単語は、短縮版が一般的に使用される場合は許可されます。
また、コメント内に完全な名前が隣接する場合は、省略形を使用できます。
17. C++ ソースコードを持つファイルは .cpp
拡張子を持つ必要があります。ヘッダーファイルは .h
拡張子を持つ必要があります。
コードの書き方
1. メモリ管理。
手動のメモリ解放 (delete
) はライブラリコードでのみ使用できます。
ライブラリコードでは、delete
演算子はデストラクタでのみ使用されます。
アプリケーションコードでは、メモリはそれを所有するオブジェクトによって解放されなければなりません。
例:
- 一番簡単な方法は、オブジェクトをスタック上に配置するか、別のクラスのメンバーにすることです。
- 小さなオブジェクトを多数保持する場合、コンテナを利用してください。
- ヒープに存在する少数のオブジェクトを自動的に解放する場合は
shared_ptr/unique_ptr
を使用してください。
2. リソース管理。
RAII
を利用し、上記を参照してください。
3. エラーハンドリング。
例外を使用します。ほとんどの場合、例外をスローするだけで、キャッチする必要はありません(RAII
のため)。
オフラインデータ処理アプリケーションでは、例外をキャッチしないのがしばしば許可されます。
ユーザーのリクエストを処理するサーバーでは、通常は接続ハンドラーのトップレベルで例外をキャッチすれば十分です。
スレッド関数内では、例外をキャッチして保持してから、join
後にメインスレッドに再スローする必要があります。
例外を処理せずに隠さないでください。すべての例外をただ盲目的にログに記録しないでください。
無視する必要があるいくつかの例外がある場合は、特定のものに対してのみそうし、その他を再スローしてください。
応答コードや errno
を持つ関数を使用する場合は、常に結果をチェックし、エラーがあれば例外をスローしてください。
コード内の不変条件を確認するために assert を使用できます。
4. 例外の型。
アプリケーションコードでは複雑な例外の階層を使用する必要はありません。例外メッセージはシステム管理者が理解できるものでなければなりません。
5. デストラクタから例外をスローする。
これは推奨されていませんが、許可されています。
次のオプションを使用してください:
- 例外につながる可能性のあるすべての作業を事前に行う関数 (
done()
またはfinalize()
) を作成します。その関数が呼び出された場合、後のデストラクタでは例外が発生することはありません。 - ネットワーク経由でメッセージを送信するなど、複雑すぎるタスクは、クラスのユーザーが破棄前に呼び出す必要がある別のメソッドに入れることができます。
- デストラクタで例外が発生した場合、それを隠すのではなくログに記録する方が良いです(ロガーが利用可能な場合)。
- シンプルなアプリケーションでは、ケースが
noexcept
の場合に関してはstd::terminate
に依存することが受け入れられます(C++11 のデフォルト)。
6. 無名のコードブロック。
特定の変数をローカルにするために、単一の関数内で別のコードブロックを作成することができます。これにより、ブロックから退出する際にデストラクタが呼び出されます。
7. マルチスレッド。
オフラインデータ処理プログラムでは:
- 単一の CPU コアで可能な限り最適なパフォーマンスを得るようにします。その後、必要に応じてコードを並列化できます。
サーバーアプリケーションでは:
- リクエストを処理するためにスレッドプールを使用します。この段階では、ユーザー空間のコンテキストスイッチが必要なタスクはありません。
フォークは並列化には使用されません。
8. スレッドの同期。
異なるスレッドが異なるメモリセルを使用する(さらに良いことに、異なるキャッシュラインを使用する)ことが可能であり、スレッドの同期を使用しないことが多いです(joinAll
を除く)。
同期が必要な場合、ほとんどのケースでは、lock_guard
下のミューテックスを使用するだけで十分です。
他のケースでは、システム同期プリミティブを使用してください。ビジーウェイトを使用しないでください。
原子的な操作は、最も簡単な場合にのみ使用されるべきです。
ロックフリーのデータ構造を実装しようとしないでください。これはあなたの主要な専門分野である場合を除きます。
9. ポインタと参照。
ほとんどの場合、参照を優先してください。
10. const
。
定数参照、定数ポインタ、const_iterator
、および const
メソッドを使用します。
const
をデフォルトとみなし、必要な場合にのみ非 const
を使用してください。
変数を値として渡す場合、const
を使用することは通常意味がありません。
11. 無符号。
必要に応じて unsigned
を使用してください。
12. 数値型。
UInt8
、UInt16
、UInt32
、UInt64
、Int8
、Int16
、Int32
、および Int64
、さらに size_t
、ssize_t
、ptrdiff_t
を使用します。
以下の数値にはこれらの型を使用しないでください: signed/unsigned long
、long long
、short
、signed/unsigned char
、char
。
13. 引数の渡し方。
複雑な値を移動する場合は値として渡し、ループ内で値を更新したい場合は参照として渡します。
ヒープに作成されたオブジェクトの所有権を関数が取得する場合、引数の型を shared_ptr
または unique_ptr
にします。
14. 戻り値。
ほとんどの場合、return
を単に使用します。return std::move(res)
と書かないでください。
関数がヒープ上にオブジェクトを割り当ててそれを返す場合、shared_ptr
または unique_ptr
を使用します。
稀なケース(ループ内で値を更新する場合)では、引数を経由して値を返す必要があるかもしれません。この場合、引数は参照であるべきです。
15. namespace
。
アプリケーションコードに専用の namespace
を使用する必要はありません。
小さなライブラリにもこの必要はありません。
中規模から大規模なライブラリの場合、すべてを namespace
に入れてください。
ライブラリの .h
ファイルでは、namespace detail
を使用して、アプリケーションコードに必要のない実装の詳細を隠すことができます。
.cpp
ファイルでは、シンボルを隠すために static
や無名の namespace
を使用できます。
また、enum
のために namespace
を使用して、対応する名前が外部の namespace
に入らないようにすることもできます(ただし、enum class
を使用する方が良いです)。
16. 遅延初期化。
初期化に引数が必要な場合、通常、デフォルトコンストラクタを書くべきではありません。
もし後で初期化を遅らせる必要がある場合は、無効なオブジェクトを作成するデフォルトコンストラクタを追加できます。または、少数のオブジェクトに対しては shared_ptr/unique_ptr
を使用できます。
17. 仮想関数。
クラスが多態的な使用を意図していない場合、関数を仮想にする必要はありません。これはデストラクタにも当てはまります。
18. エンコーディング。
至る所で UTF-8 を使用します。std::string
および char *
を使用してください。std::wstring
および wchar_t
は使用しないでください。
19. ロギング。
コード内の例を参照してください。
コミットする前に、すべての意味のないデバッグログやその他のデバッグ出力を削除してください。
サイクル内でのログは、Trace レベルでさえ避けるべきです。
ログはどのロギングレベルでも可読性がなければなりません。
ロギングは、ほとんどの場合、アプリケーションコードでのみ使用されるべきです。
ログメッセージは英語で書かなければなりません。
ログはできるだけシステム管理者が理解できるようにするべきです。
ログ内で不適切な言葉を使用しないでください。
ログ内では UTF-8 エンコーディングを使用してください。稀に、ログに非 ASCII 文字を使用できます。
20. 入出力。
アプリケーションパフォーマンスにクリティカルな内部ループで iostreams
は使用しないでください(stringstream
も使用しないでください)。
代わりに DB/IO
ライブラリを使用してください。
21. 日付と時刻。
DateLUT
ライブラリを参照してください。
22. include。
インクルードガードの代わりに #pragma once
を常に使用してください。
23. using。
using namespace
は使用しません。特定のものを using
することができます。しかし、クラスまたは関数の内部でローカルにしてください。
24. 必要がない限り、関数の trailing return type
を使用しないでください。
25. 変数の宣言と初期化。
26. 仮想関数については、基本クラスで virtual
を記述し、子孫クラスでは virtual
の代わりに override
を記述します。
C++ の未使用機能
1. 仮想継承は使用されていません。
2. 現代の C++ で便宜的にシンタクスシュガーを持つ構文(例):
プラットフォーム
1. 特定のプラットフォーム向けにコードを記述します。
ただし、他の条件が同じであれば、クロスプラットフォームまたはポータブルなコードが好まれます。
2. 言語:C++20(利用可能な C++20 機能 を参照)。
3. コンパイラ: clang
。執筆時点(2025年3月)、コードは clang バージョン >= 19 を使用してコンパイルされています。
標準ライブラリ (libc++
) が使用されます。
4. OS: Linux Ubuntu、Precise より古くないバージョンです。
5. コードは x86_64 CPU アーキテクチャ向けに記述されています。
CPU 命令セットは、私たちのサーバーでサポートされている最小限のセットです。現在は SSE 4.2 です。
6. -Wall -Wextra -Werror -Weverything
コンパイルフラグを、いくつかの例外を除いて使用します。
7. 静的リンクをすべてのライブラリに使用しますが、静的に接続するのが困難なライブラリは除外します(ldd
コマンドの出力を参照)。
8. コードはリリース設定で開発およびデバッグされます。
ツール
1. KDevelop は良い IDE です。
2. デバッグのためには gdb
、valgrind
(memcheck
)、strace
、-fsanitize=...
、または tcmalloc_minimal_debug
を使用してください。
3. プロファイリングには Linux Perf
、valgrind
(callgrind
)、または strace -cf
を使用します。
4. ソースは Git にあります。
5. アセンブリには CMake
を使用します。
6. プログラムは deb
パッケージを使用してリリースされます。
7. マスターへのコミットはビルドを壊してはいけません。
ただし、作業可能と見なされるのは選ばれたリビジョンのみです。
8. コードが完全に準備が整っていなくても、できるだけ頻繁にコミットを行ってください。
その目的のためにブランチを使用します。
マスター ブランチのコードがまだビルドできない場合は、プッシュ前にビルドから除外してください。数日以内に仕上げるか、削除する必要があります。
9. 重要でない変更にはブランチを使用し、それらをサーバーに公開してください。
10. 未使用のコードはリポジトリから削除されます。