一般的な推奨事項
フォーマット
clang-format によって自動的に処理されます。
2. インデントは4スペースです。タブキーを押すと4スペース分が挿入されるよう、開発環境を設定してください。
3. 開き波括弧と閉じ波括弧は、それぞれ独立した行に記述してください。
ステートメントである場合、1行に記述できます。波括弧の前後にスペースを入れてください (行末のスペースは除く) 。
if、for、while などの式では、開き括弧の前にスペースを挿入します (関数呼び出しとは異なります) 。
+、-、*、/、% など) および三項演算子 ?: の前後にスペースを入れてください。
.、-> の前後にスペースを使用しないでください。
必要に応じて、演算子を次の行に折り返すことができます。この場合、その前のインデントが深くなります。
11. 単項演算子 (--、++、*、& など) と引数の間にスペースを使用しないでください。
12. カンマの後にはスペースを入れ、前には入れないこと。同じルールが for 式内のセミコロンにも適用される。
13. [] 演算子の前後にスペースを入れないでください。
14. template <...> という式では、template と < の間にスペースを入れる。< の後や > の前にはスペースを入れない。
public、private、protected を class/struct と同じレベルに記述し、残りのコードはインデントする。
namespace が使用されており、他に特筆すべき内容がない場合、namespace 内でインデントは不要です。
17. if、for、while、またはその他の式のブロックが単一のstatementで構成される場合、波括弧は省略できます。その場合、statementは別の行に記述してください。このルールは、ネストされたif、for、whileなどにも適用されます。
ただし、内側のステートメントに波括弧またはelseが含まれる場合、外側のブロックも波括弧で記述する必要があります。
A const は、型名の前に記述しなければなりません。
* と & の両側にスペースを入れる必要があります。
using キーワードで別名を付けます (ごく単純な場合を除く) 。
つまり、テンプレートパラメータは using でのみ指定し、コード内で繰り返し記述しません。
using は、関数内などでローカルに宣言できます。
コメント
/// で始め、複数行コメントは /** で始めます。これらのコメントは “ドキュメント用” と見なされます。
注: これらのコメントからドキュメントを生成するために Doxygen を使用できます。ただし、IDE 上でコードをたどるほうが便利なため、Doxygen は一般には使われません。
9. 複数行コメントの先頭と末尾には空行を入れてはいけません (複数行コメントを閉じる行は除きます) 。
10. コードをコメントアウトする場合は、“ドキュメント用” コメントではなく、通常のコメントを使用してください。
11. コメントアウトしたコードは、コミット前に削除してください。
12. コメントやコードに暴言を書かないでください。
13. 大文字は使わないでください。句読点を過剰に使わないでください。
名前
using は、クラスと同じ規則で命名します。
5. Template 型引数の名前: 単純な場合は、T、T、U、T1、T2 を使用します。
より複雑な場合は、クラス名の規則に従うか、プレフィックスとして T を付けます。
N を使用します。
I プレフィックスを付けることができます。
define とグローバル定数の名前は、アンダースコアで区切った ALL_CAPS を使用します。
- 変数名では、略語は小文字にします
mysql_connection(mySQL_connectionではありません) 。 - クラス名と関数名では、略語の大文字はそのまま維持します
MySQLConnection(MySqlConnectionではありません) 。
enum の定数には、先頭を大文字にした CamelCase を使用します。ALL_CAPS も使用可能です。enum がローカルでない場合は、enum class を使用します。
AST, SQL.
Not NVDH (意味のない適当な文字列)
短縮した形が一般的に使われているなら、省略形の語も使用できます。
コメント内に正式名称を併記している場合は、略語を使用してもかまいません。
17. C++ のソースコードのファイル名には .cpp 拡張子を付ける必要があります。ヘッダーファイルには .h 拡張子を付ける必要があります。
コードの書き方
delete) は、ライブラリコードでのみ使用できます。
ライブラリコードでは、delete 演算子を使用できるのはデストラクタ内だけです。
アプリケーションコード では、メモリはそれを所有するオブジェクトが解放する必要があります。
例:
- 最も簡単なのは、オブジェクトをスタック上に置くか、別のクラスのメンバーにすることです。
- 小さなオブジェクトが大量にある場合は、コンテナーを使用します。
- ヒープ上にある少数のオブジェクトを自動的に解放するには、
shared_ptr/unique_ptrを使用します。
RAII を使用してください。詳細は上記を参照してください。
3. エラー処理。
例外を使用します。ほとんどの場合、必要なのは例外を throw することだけで、catch する必要はありません (RAII のためです) 。
オフラインのデータ処理アプリケーションでは、例外を catch しなくても問題ないことがよくあります。
ユーザーリクエストを処理するサーバーでは、通常、connection ハンドラーの最上位レベルで例外を catch すれば十分です。
スレッド関数では、すべての例外を catch して保持し、join の後でメインスレッドで再度 throw するようにしてください。
errno を返す関数を使用する場合は、必ず結果を確認し、エラー時には例外を送出してください。
assert を使用できます。
4. 例外の種類。
アプリケーションコードで複雑な例外階層を使う必要はありません。例外メッセージは、システム管理者が理解できるものであるべきです。
5. デストラクタからの例外送出。
これは推奨されませんが、許容されています。
次の方法があります。
- 例外につながる可能性のある処理を事前にすべて行う関数 (
done()またはfinalize()) を作成します。その関数が呼び出されていれば、その後デストラクタで例外が発生しないようにします。 - 複雑すぎる処理 (ネットワーク経由でのメッセージ送信など) は、オブジェクトが破棄される前にクラスの利用者が呼び出す別のメソッドに切り出すことができます。
- デストラクタ内で例外が発生した場合は、それを隠すよりログに記録するほうが望ましいです (ロガーが利用可能な場合) 。
- 単純なアプリケーションでは、例外処理を
std::terminate(C++11 ではnoexceptがデフォルトであるため) に任せてもかまいません。
- まずは単一の CPU コアで可能な限り高い性能を出せるようにしてください。そのうえで、必要に応じてコードを並列化できます。
- リクエストの処理にはスレッドプールを使用してください。これまでのところ、ユーザー空間でのコンテキストスイッチを必要とするタスクはありませんでした。
joinAll を除く) 。
同期が必要な場合でも、ほとんどのケースでは lock_guard の下で mutex を使えば十分です。
それ以外の場合は、システムの同期プリミティブを使用してください。ビジーウェイトは使用しないでください。
アトミック操作は、最も単純なケースでのみ使用してください。
それがあなたの主たる専門分野でない限り、ロックフリーなデータ構造を実装しようとしないでください。
9. ポインタと参照。
ほとんどの場合、参照を優先してください。
10. const。
定数参照、定数へのポインタ、const_iterator、および const メソッドを使用してください。
const をデフォルトと考え、必要な場合にのみ非 const を使用してください。
変数を値渡しする場合、const を付けても通常は意味がありません。
11. unsigned。
必要な場合にのみ 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. 引数の受け渡し。
複雑な値は、ムーブする場合は値渡しにして std::move を使用してください。ループ内で値を更新したい場合は、参照渡しにしてください。
関数がヒープ上で作成されたオブジェクトの所有権を受け取る場合は、引数の型を shared_ptr または unique_ptr にしてください。
14. 戻り値。
ほとんどの場合、単に return を使ってください。return std::move(res) と書かないでください。
関数がヒープ上にオブジェクトを確保してそれを返す場合は、shared_ptr または unique_ptr を使用してください。
まれに (ループ内で値を更新する場合) 、引数経由で値を返す必要があることがあります。この場合、その引数は参照にするべきです。
namespace。
アプリケーションコードのために、別の namespace を使う必要はありません。
小規模なライブラリでも同様に不要です。
中規模から大規模のライブラリでは、すべてを namespace に入れてください。
ライブラリの .h ファイルでは、アプリケーションコードに不要な実装の詳細を隠すために namespace detail を使えます。
.cpp ファイルでは、シンボルを隠すために static または無名 namespace を使えます。
また、対応する名前が外側の namespace に出てしまうのを防ぐために、enum に対して namespace を使うこともできます (ただし、enum class を使うほうが望ましいです) 。
16. 遅延初期化。
初期化に引数が必要なら、通常はデフォルトコンストラクタを書くべきではありません。
後で初期化を遅らせる必要が生じた場合は、無効なオブジェクトを作るデフォルトコンストラクタを追加できます。あるいは、オブジェクト数が少ないなら、shared_ptr/unique_ptr を使うこともできます。
std::string と char * を使用してください。std::wstring と wchar_t は使用しないでください。
19. ロギング。
コード中の例を参照してください。
コミット前に、無意味なログやデバッグ用のログ、その他あらゆるデバッグ出力をすべて削除してください。
ループ内でのログ出力は、Trace レベルであっても避けるべきです。
ログは、どのログレベルでも読みやすくなければなりません。
ログは、基本的にはアプリケーションコードでのみ使用すべきです。
ログメッセージは英語で記述しなければなりません。
ログは、できればシステム管理者が理解できる内容であるべきです。
ログで下品な表現を使わないでください。
ログでは UTF-8 エンコーディングを使用してください。まれに、ログで非 ASCII 文字を使用してもかまいません。
20. 入出力。
アプリケーションの性能にとって重要な内部ループでは iostreams を使わないでください (また、stringstream は絶対に使わないでください) 。
代わりに DB/IO ライブラリを使用してください。
21. 日付と時刻。
DateLUT ライブラリを参照してください。
22. include。
include ガードではなく、常に #pragma once を使用してください。
23. using。
using namespace は使用しません。特定の対象に対して using を使うことはできます。ただし、クラスまたは関数の内部に限定してください。
24. 必要な場合を除き、関数で trailing return type を使用しないでください。
virtual を記述し、派生クラスでは virtual ではなく override を記述します。
C++で使用していない機能
プラットフォーム
clang。本稿執筆時点 (2025年3月) では、コードは clang バージョン 19 以上でコンパイルされています。
標準ライブラリには libc++ を使用します。
4. OS: Ubuntu Linux、Precise 以降。
5. コードは x86_64 CPU アーキテクチャ向けに記述します。
CPU 命令セットは、当社サーバーでサポートされる最小構成に合わせます。現在は SSE 4.2 です。
6. 一部の例外を除き、-Wall -Wextra -Werror -Weverything コンパイルフラグを使用します。
7. 静的リンクが難しいものを除き、すべてのライブラリで静的リンクを使用します (ldd コマンドの出力を参照) 。
8. コードの開発とデバッグはリリース設定で行います。
ツール
gdb、valgrind (memcheck) 、strace、-fsanitize=...、または tcmalloc_minimal_debug を使用してください。
3. プロファイリングには、Linux Perf、valgrind (callgrind) 、または strace -cf を使用してください。
4. ソースコードは Git で管理されています。
5. ビルドには CMake を使用します。
6. プログラムは deb パッケージとしてリリースされます。
7. master へのコミットでビルドを壊してはなりません。
ただし、正常に動作すると見なされるのは一部のリビジョンだけです。
8. コードがまだ完全でなくても、できるだけ頻繁にコミットしてください。
この目的にはブランチを使用してください。
master ブランチ内のコードがまだビルドできない場合は、push の前にそのコードをビルド対象から外してください。数日以内に仕上げるか、削除する必要があります。
9. 単純ではない変更には、ブランチを使用し、サーバーに公開してください。
10. 未使用のコードはリポジトリから削除されます。
ライブラリ
boost および Poco フレームワークも使用します。
2. OS パッケージのライブラリは使用できません。あらかじめインストールされているライブラリも使用できません。すべてのライブラリは contrib ディレクトリにソースコードとして配置し、ClickHouse と一緒にビルドする必要があります。詳しくは、新しいサードパーティライブラリの追加に関するガイドラインを参照してください。
3. すでに使用されているライブラリを常に優先します。
一般的な推奨事項
using を使ってください。
5. 可能であれば、コピーコンストラクター、代入演算子、デストラクタ (クラスに少なくとも 1 つの virtual 関数がある場合の virtual デストラクタを除く) 、ムーブコンストラクター、ムーブ代入演算子は書かないでください。言い換えると、コンパイラが生成する関数で正しく動作するようにすべきです。default を使えます。
6. コードの簡素化を心がけてください。可能な場合はコード量を減らしてください。
追加の推奨事項
stddef.h の型に std:: を明示的に付けること
は推奨されません。つまり、std::size_t ではなく、より短い size_t と書くことを推奨します。
ただし、std:: を付けても構いません。
2. 標準 C ライブラリの関数に std:: を明示的に付けること
は推奨されません。つまり、std::memcpy ではなく memcpy と書いてください。
その理由は、memmem のような類似の非標準関数があるためです。これらの関数を使うこともあります。こうした関数は namespace std には存在しません。
どこでも memcpy ではなく std::memcpy と書いていると、std:: なしの memmem が不自然に見えてしまいます。
とはいえ、好みで std:: を使っても問題ありません。
3. 標準 C++ ライブラリに同等のものがある場合に C の関数を使うこと。
より効率的であれば、これは許容されます。
たとえば、大きなメモリ chunk をコピーする場合は、std::copy ではなく memcpy を使います。
4. 複数行の関数引数。
次のいずれの折り返しスタイルも使用できます。