04.C++ コーディング規約
このドキュメントでは、本プロジェクトでC++を記述する際の、コーディングスタイルと規約について定めます。モダンC++ (C++17以降) の機能を最大限に活用し、安全性、保守性、パフォーマンスの高いコードを目指します。
Note
"共通原則およびC言語規約との関係" - 本規約は、01.共通コーディング原則 をC++言語に特化・具体化したものです。- C言語と共通する部分(命名規則、ヘッダーファイルのインクルードガードなど)については、C言語コーディング規約 も参考にしてください。
1. 基本方針 (Guiding Principles)
- 本プロジェクトのC++コードは、Bjarne Stroustrupらが主導するC++ Core Guidelinesの思想に準拠し、 その根底にある安全性、保守性(可読性)、パフォーマンスの最適なバランスを追求します。
- 可能な限り、C++17以降の標準に準拠し、その機能を活用することを推奨します。
Tip
"原則が衝突する場合の指針" 稀に、これらの原則が互いに衝突する場合があります(例: 高い移植性を維持するためのコードが、 特定の環境での最高の安全性や可読性を少し損なうケース)。そのような場合は、独断で判断せず、チームで議論することを推奨します。 最終的に何らかのトレードオフを受け入れる決定をした際は、なぜその選択をしたのかをコードコメントに明記し、将来の保守者がその設計意図を理解できるようにしてください。
2. レイアウトと書式設定 (Layout and Formatting)
手作業でのスタイル遵守は非効率であり、レビューのノイズとなるため、本プロジェクトではツールによる規約の遵守を強制します。
各ツールの設定は、リポジトリのルートに配置された.clang-format ファイル(フォーマッター)および .clang-tidy
ファイル(静的解析)で一元管理します。また、エディタレベルでの基本的な設定は
.editorconfig ファイルで統一します。
-
フォーマッター:
ClangFormat- 役割:
プロジェクトのスタイルを定義した
.clang-formatファイルに基づき、コードの見た目を自動で統一します。 - 運用: VSCodeのC/C++拡張機能と連携し、ファイル保存時に自動でフォーマットを適用します。CI/CDでフォーマットが遵守されているかをチェックします。
- 役割:
プロジェクトのスタイルを定義した
-
静的解析 (主):
Clang-Tidy- 役割: Clangコンパイラ基盤の強力な静的解析ツールです。潜在的なバグ、パフォーマンスの問題、コーディングスタイルの違反、モダンC++への移行支援など、非常に広範なチェックを行います。
- 運用: プロジェクトの品質の主軸として利用します。CI/CDプロセスに組み込み、重大な警告はエラーとして扱うことを強く推奨します。
-
静的解析 (補):
Cppcheck- 役割: 誤検出が少ないことに定評のある、高速な静的解析ツールです。特にメモリリークやリソースリークといった、古典的だが重大なバグの発見に優れています。
- 運用:
Clang-Tidyを補完する「セカンドオピニオン」としてCI/CDプロセスに組み込みます。Clang-Tidyとは異なる観点からコードをチェックすることで、解析の網羅性を高めます。
!!! success "CI/CDによる自動チェック" GitHub
Actionsのワークフローにclang-format --dry-run --Werror、clang-tidy、およびcppcheckを組み込むことで、フォーマット・コード品質・静的解析が
規約に違反しているコードのマージを自動的にブロックします。重大な警告はエラーとして扱う設定を必須とします。
3. 命名規則 (Naming Conventions)
snake_case(スネークケース):- 関数名、メソッド名、変数名。
PascalCase(パスカルケース):- クラス名、構造体名、
enum class名。
- クラス名、構造体名、
UPPER_SNAKE_CASE(大文字のスネークケース):- マクロ、
enum classのメンバー。
- マクロ、
- メンバ変数 (Member Variables):
snake_case_のように、末尾にアンダースコアを付けます。
4. コメント (Comments)
- ドキュメントコメントの不使用:
- 本プロジェクトでは、「仕様は
Docs/フォルダに集約する」という原則に基づき、Doxygen形式のようなAPIドキュメント自動生成のためのコメントは原則として使用しません。
- 本プロジェクトでは、「仕様は
- 通常のコメント (
//または/* ... */):- コードが「何をしているか」よりも「なぜそうしているのか」という設計意図や背景、複雑なアルゴリズムの要点を説明するために使用します。
-
機能IDとの連携:
- 01.共通コーディング原則で定められた通り、機能の実装には、対応する機能IDをコメントとして明記します。
// FUNC-AUTH-1-1: ユーザー名とパスワードで認証を行う bool authenticate_user(const std::string& username, const std::string& password) { // なぜこの処理が必要か、という意図を記述... // ... }
5. 言語機能の利用方針 (Language Features)
5.1. リソース管理: RAIIとスマートポインタ
!!! success "RAIIの徹底" RAII (Resource Acquisition Is Initialization) は、モダンC++における最も重要な原則の一つです。リソース(メモリ、ファイル、ソケット等)の生存期間をオブジェクトの生存期間に束ねることで、リソースリークや二重解放といったバグを根絶します。
- RAIIの徹底:
- リソースは、その生存期間を管理するオブジェクト(クラス)のコンストラクタで取得し、デストラクタで解放するRAIIパターンを徹底します。
-
手動の
new/deleteは原則禁止:- メモリ管理には、以下のスマートポインタを必ず使用してください。これにより、手動での
delete呼び出し忘れによるメモリリークを防ぎます。 std::unique_ptr: リソースの唯一の所有権を示す場合の第一選択。軽量でオーバーヘッドがほとんどありません。std::shared_ptr: 複数のポインタがリソースの所有権を共有する必要がある、限定的な場合に使用します。std::make_unique/std::make_shared: スマートポインタを作成する際は、例外安全性を高めるため、これらのヘルパー関数を使用します。
// 良い例 auto user_ptr = std::make_unique<User>("Taro"); // 悪い例: deleteし忘れる可能性がある // User* raw_ptr = new User("Jiro"); - メモリ管理には、以下のスマートポインタを必ず使用してください。これにより、手動での
5.2. 推奨される言語機能
autoキーワード: 型が右辺から自明な場合や、イテレーターなどの複雑な型を扱う場合は、autoを積極的に利用し、コードの冗長性を減らします。-
範囲ベースforループ: コンテナの全要素を走査する場合は、インデックスベースの古い
forループではなく、範囲ベースforループを使用します。std::vector<int> numbers = {1, 2, 3}; // 良い例 for (const auto& num : numbers) { ... } // 悪い例 // for (size_t i = 0; i < numbers.size(); ++i) { ... } -
nullptrの使用: ヌルポインターを示す場合は、C言語由来のNULLマクロではなく、型安全なnullptrを必ず使用します。 constとconstexpr: 変更しない変数にはconstを、コンパイル時定数にはconstexprを積極的に利用し、不変性(Immutability)を高めます。enum classの使用: 型安全でない古いenumではなく、スコープを持ち、暗黙的な整数変換のないenum classを使用します。-
構造化束縛 (C++17):
std::pairやstd::tupleから複数の値を取り出す際は、構造化束縛を利用してコードを簡潔にします。std::map<int, std::string> user_map; // 良い例 auto [iter, success] = user_map.insert({1, "Taro"});
5.3. クラス設計
overrideとfinal: 仮想関数をオーバーライドする際はoverrideキーワードを、これ以上派生させないクラスや仮想関数にはfinalキーワードを明記し、意図を明確にします。defaultとdelete: コンパイラが自動生成する特殊なメンバー関数を明示的にデフォルト実装させたい場合は= default;を、禁止したい場合は= delete;を使用します(例: コピーコンストラクタの禁止)。
5.4. ヘッダーファイル (Header Files)
- インクルードガードや
#pragma onceの利用、#includeの順序については、C言語コーディング規約 と同様のルールを適用します。本プロジェクトでは#pragma onceの利用を推奨します。
5.5. 名前空間 (Namespaces)
using namespaceディレクティブは、ヘッダーファイル (.h,.hpp) 内での使用を禁止します。- 理由: ヘッダーファイルで
using namespaceを使用すると、そのヘッダーをインクルードした全てのファイルで名前空間が汚染され、意図しない名前の衝突を引き起こす可能性があります。 - 代わりに、
std::vectorやstd::stringのように、常にプレフィックスを付けて使用してください。 - ソースファイル (
.cpp) 内で使用する場合も、関数のスコープ内に限定するなど、影響範囲を最小限に留めることを推奨します。
// 良い例 (ソースファイル内)
#include <vector>
#include <string>
void my_function()
{
using std::vector; // この関数内でのみ、vectorをプレフィックスなしで使える
vector<std::string> names;
// ...
}
6. エラー処理と例外 (Error Handling and Exceptions)
- エラーは、例外を用いて明確に通知することを基本とします。戻り値でのエラーコードは、パフォーマンスが極めて重要な場合や、C言語ライブラリとの連携など、限定的な場面でのみ使用します。
std::runtime_errorやstd::invalid_argumentなど、標準ライブラリの例外クラスを適切に使い分けます。- 例外を送出しないことが保証される関数には、
noexceptを明記し、コンパイラの最適化を助けます。 - デストラクタから例外を送出してはいけません。
7. 安全なコーディングプラクティス (Safe Coding Practices)
7.1. コンパイラ警告 (Compiler Warnings)
- コンパイラの警告レベルは、可能な限り高く設定します。(GCC/Clang:
-Wall -Wextra, MSVC:/W4または/Wall) - 全ての警告はエラーとして扱い (
-Werror)、修正することを原則とします。
7.2. constの積極的な利用 (Use of const)
-
変更されるべきでない変数や引数に加え、メンバ変数を変更しないメンバ関数にも
constを付け、意図しない状態変更を防ぎます。class User { public: // このメソッドはメンバ変数を変更しないことを保証する std::string get_name() const; private: std::string name_; };
7.3. C言語スタイルの危険な操作の回避
- Cスタイルキャストの禁止:
(type)valueのようなCスタイルキャストは型安全でないため禁止します。代わりにstatic_cast,reinterpret_cast,const_castを適切に使い分けます。 - ポインタ演算の制限:
生ポインタの直接的な演算は、バッファオーバーランなどの脆弱性の原因となります。可能な限り
std::vectorやstd::stringなどのコンテナクラスと、そのイテレータを使用してください。 - 可変長引数 (
...) の回避: 型安全でないC言語の可変長引数は避け、代わりにstd::vectorやstd::initializer_listを使用します。
8. パフォーマンスに関する考慮事項 (Performance Considerations)
Warning
"早すぎる最適化は諸悪の根源" パフォーマンスの最適化は、プロファイリングによってボトルネックが特定された後に行うべきです。明確な根拠なく、可読性を犠牲にするような最適化は避けてください。
8.1. ムーブセマンティクス (Move Semantics)
- リソースの所有権を移動させる際は、高コストなコピーを避け、
std::moveを適切に利用してムーブセマンティクスを活用します。これにより、特に大きなオブジェクトを扱う際のパフォーマンスが大幅に向上します。
8.2. インライン展開 (Inlining)
- 頻繁に呼び出される小さな関数は、ヘッダーファイル内で
inlineキーワードを付けて定義することで、インライン展開を促し、関数呼び出しのオーバーヘッドを削減できます。
9. 移植性と環境依存 (Portability and Environment Dependencies)
C言語規約と同様のルールを適用します。
- データ型とサイズ:
サイズの互換性が重要な場面では、
<cstdint>のint32_tなどを利用します。 - エンディアン: ネットワーク通信などではバイトオーダーを意識し、適切に変換します。
10. 非同期処理 (Asynchronous Processing)
- 基本方針:
- C++11以降で導入された
std::async,std::future,std::promise,std::threadなどの標準ライブラリ機能を活用し、ブロッキングな処理を避け、応答性を高めます。
- C++11以降で導入された
- C++20 コルーチン:
- C++20が利用可能な環境では、
co_await,co_yield,co_returnを用いたコルーチンの利用を検討します。これにより、非同期コードを同期的コードに近い形で、より直感的に記述することが可能になります。 - ただし、標準ライブラリでのコルーチンサポートはまだ発展途上であるため、導入には十分な検討が必要です。
- C++20が利用可能な環境では、
11. その他 (Miscellaneous)
- 不変性 (Immutability):
- 変更されるべきでない変数やデータ構造に対しては、
constやconstexprを積極的に利用し、不変性を高めることを意識してください。 - これにより、意図しない副作用を防ぎ、コードの安全性が向上します。
- 変更されるべきでない変数やデータ構造に対しては、
- コメントアウトされたコードの禁止:
- 不要になったコードは、コメントアウトして残さずに削除してください。コードの履歴はGitのバージョン管理システムで追跡します。