clang-format を イイ感じに設定する
公開が遅れましたが 初心者 C++er Advent Calendar 2015 の 12日目の記事です。
C++ の良い点として、 clang-format のようなフォーマッタがある点だと思っています。 golang でも gofmt というフォーマッタがありますが、あんなかんじのやつです。 clang-format を使うことで、C/C++/Objective-C/Java/JavaScript/ProtocolBuffer などのコードフォーマッティングができます。 また、フォーマットの設定ををいろいろ変更でき、チームで設定を共有することで、 統一されたコードスタイルを実現することが可能です。
id:rhysd さんの vim-clang-format をvimに導入することで vimからも使えます。
にスタイルの内容がまとまっているので、そちらを参考するのもよさそう。 今回は、設定するに当たって、試してみないとわからない部分も多かったため、 一つ一つ試しながら挙動確認を行いました。 それぞれのスタイルのより詳細な説明を知りたい方は、上記のドキュメントを参照してもらうのが良さそうです。
インストール
$ brew update $ brew install clang-format
から /usr/local/bin
にさくっと入ります。
Homebrew で入る現在の最新バージョンが 3.8.0
であるため、以下ではこのバージョンで設定できる項目について紹介してきます。
各種設定
.clang-format
ファイルに yaml スタイルで書き、プロジェクトフォルダに配置することでスタイルが適用されます。
rhysd/vim-clang-format
を使う場合は、g:clang_format#style_options
の変数を設定することでスタイルを設定できます。詳しくはドキュメントを参照してください。以下からは各設定項目について紹介します。
設定できる項目としては、C++ 以外の設定項目もありますが、今回は C++ のものだけ紹介しています。
また、確かめてみたもののわからなかった項目もあるので、あくまで参考までにしてください...!
(AlignOperands
, AllowAllParametersOfDeclarationOnNextLine
, BreakBeforeTernaryOperators
など)
BasedOnStyle
基本スタイルとして LLVM
, Google
, Chromium
, Mozilla
, WebKit
のプロジェクトのスタイルが定義されています。これらをベースとして細かい設定を変更することができます。いろいろ設定するのが面倒な人は、とりあえずどれか選んで使いながら好きな値を設定していくほうが、面倒もなくいいと思います。
AccessModifierOffset
public:
, private:
とかのアクセス修飾子のインデント位置。クラス内で public:
などを書いた時は、IndentWidth
の値がすでにインデントされるので、その位置からのインデント。IndentWIdth
が 2
で AccessModifierOffset
が 0
だと、
class Sample { public: Sample(); ~Sample(); };
となり、
AccessModifierOffset
を -2
とかに設定すると
class Sample { public: Sample(); ~Sample(); };
こうなります。
AlignAfterOpenBracket
true
に設定した場合、 ()
, []
, {}
で改行した時に、関数の引数を整列してくれます。
AlignConsecutiveAssignments
true
に設定すると、変数への代入が連続した場合に、 =
で整列します。
int aaaa = 12; int b = 23; int ccc = 23;
これが
int aaaa = 12; int b = 23; int ccc = 23;
こうなります。
AlignEscapedNewlinesLeft
エスケープされた改行文字を左側に表示するかどうか。true
に設定すると、一番左側で整列させ、false
に設定すると、一番右側になります。
AlignOperands
true
にすると、水平方向に二項演算子、三項演算子を揃えるらしい。(ちょっとよくわかってない
AlignTrailingComments
true
に設定すると、末尾のコメント行を揃えます。
int a = 1 + 2; // 計算1 int b = 111111 + 222; // 計算2
これが
int a = 1 + 2; // 計算1 int b = 111111 + 222; // 計算2
こうなります。
AllowAllParametersOfDeclarationOnNextLine
次の行にすべてのパラメータの宣言を許可するらしい (よくわかってない
AllowShortBlocksOnASingleLine
if f(a) { return 0; }
みたいな簡単な if 文を
if (a) { return; }
のように1行で表示します。
AllowShortCaseLabelsOnASingleLine
true
に設定すると、短い case ラベルを一行で表示します。
switch (a) { case A: break; }
が
switch (a) { case A: break; }
こうなります。
AllowShortFunctionsOnASingleLine
None
, Empty
, Inline
, All
から選べます。短い関数を1行で表示するかどうかのオプションです。None
を指定すると、1行では表示しない。Empty
を指定すると、空の関数のみ1行に。Inline
を指定すると、クラス内の関数だけ1行に。All
は1行で表示できるものをすべて1行にして表示するといったものです。
AllowShortIfStatementsOnASingleLine
短い if 文を1行で表示します。true
に設定すると、
if (a) return 0;
が
if (a) return 0;
こうなります。
AllowShortIfStatementsOnASingleLine
短い while 文を1行で表示します。true
に設定すると
while (a) continue;
が
while (a) continue;
こうなります。
AlwaysBreakAfterDefinitionReturnType
true
にすると、関数定義の返り値の型定義の後に改行します。
void main()
{
}
が
void
main()
{
}
こうなります。
AlwaysBreakBeforeMultilineStrings
auto a = "aaaaaaaaaaaaaaaaaaaaaaaaaaa" "aaa";
としていた場合に true
にすると
auto a = "aaaaaaaaaaaaaaaaaaaaaaaaaaa" "aaa";
こうなり、false
では
auto a = "aaaaaaaaaaaaaaaaaaaaaaaaaaa" "aaa";
こうなります。
AlwaysBreakTemplateDeclarations
template<...>
の宣言の後に改行するかどうか
template <typename T> class A;
が、 true
に設定している場合
template <typename T> class A;
となります。
BinPackArguments
false
に設定した場合、関数の引数が1行にまとめられるか、すべて1行ずつ表示されます。
つまり引数を binpack するかどうかですね。
auto a = function(aaaaaaaaaaaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbbbbbbbbbbb, ccccccccccccccccccccccccc, ddddddddddddddddddddddddd, eeeeeeeeeeeeeeeeeeeeeeeee, ddddddddddddddddddddddddd);
が true
の場合は
auto a = function(aaaaaaaaaaaaaaaaaaaaaaaaa, bbbbbbbbbbbbbbbbbbbbbbbbb, ccccccccccccccccccccccccc,
ddddddddddddddddddddddddd, eeeeeeeeeeeeeeeeeeeeeeeee, ddddddddddddddddddddddddd);
となりますが false
の場合は
auto a = function(aaaaaaaaaaaaaaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbbbbbbb,
ccccccccccccccccccccccccc,
ddddddddddddddddddddddddd,
eeeeeeeeeeeeeeeeeeeeeeeee,
ddddddddddddddddddddddddd);
こうなります。
BinPackParameters
false
に設定した場合、関数宣言や関数定義のパラメータを1行にまとめるか、すべて1行ずつ表示となります。
true
に設定した場合、うまい感じに詰められて表示されます。
void a(const std::string& bbbbbbbbbbbbbbbbbbbbbbbbb, const std::string& ccccccccccccccccccccccccc, const std::string& ddddddddddddddddddddddddd);
とかが、 true
にしてると
void a(const std::string& bbbbbbbbbbbbbbbbbbbbbbbbb, const std::string& ccccccccccccccccccccccccc, const std::string& ddddddddddddddddddddddddd);
となり、false
だと
void a(const std::string& bbbbbbbbbbbbbbbbbbbbbbbbb, const std::string& ccccccccccccccccccccccccc, const std::string& ddddddddddddddddddddddddd);
となります。
BreakBeforeBinaryOperators
二項演算子をどう折り返すかの方法となります。
const auto a = 1000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 11111111111111111111111111111111111111111111111111111;
たとえば上のようなコードがあった場合に、
const auto a = 1000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 11111111111111111111111111111111111111111111111111111;
None
を指定していると、演算子の後に改行します。
const auto a = 1000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 11111111111111111111111111111111111111111111111111111;
NonAssignment
だと、代入演算子以外の前で改行します。
All
だと、演算子の前に改行します。
BreakBeforeBraces
波括弧{}
の改行をどうするか。
Attach
の場合、波括弧は常に周囲のコンテキストにくっつけます。
void function(const std::string& aaaa) { // do something return; }
こんな風に、波括弧が前の行にくっついてます。
Linux
はAttach
と似ていますが、関数や名前空間、クラス定義の前は改行します。
Mozilla
も Attach
と似ていますが、 enum
、関数定義などの前には改行します。
Stroustrup
でも Attach
と似ていますが、catch
, else
などの前は改行します。
Allman
は、波括弧の前はすべて改行します。
GNU
では、波括弧の前はすべて改行するとともに、新たにインデントを加えます。ただし、クラス定義や関数定義などの場合は除きます。
BreakBeforeTernaryOperators
三項演算子の前に改行するかどうか。(ちょっとよくわかってない
BreakConstructorInitializersBeforeComma
コンストラクタの変数初期化のコンマの前で改行し、コンマとコロンを整列させるかどうか
true
にしていた場合
A::A() : some_variable{} , some_variable2{} , some_variable3{} , some_variable4{} , some_variable5{} { }
となり、 false
の場合は
A::A() : some_variable{}, some_variable2{}, some_variable3{}, some_variable4{}, some_variable5{} { }
となります。
ColumnLimit
行の長さの上限値。0 に設定した場合は、上限なしとなります。
CommentPragmas
特別な意味を持つコメントで、行の分割などをできないものを表現するための正規表現。
^
などを指定すると、すべてのコメントが勝手に改行されなくなります。
ConstructorInitializerAllOnOneLineOrOnePerLine
コンストラクタの初期化部分が行に収まらない場合、1行ずつ表示する。
A::A() : some_variable{}, some_variable2{}, some_variable3{}, some_variable4{}, some_variable5{} { }
が、true
の場合
A::A() : some_variable{}, some_variable2{}, some_variable3{}, some_variable4{}, some_variable5{} { }
となり、false
の場合
A::A() : some_variable{}, some_variable2{}, some_variable3{}, some_variable4{}, some_variable5{} { }
となります。
ConstructorInitializerIndentWidth
コンストラクタ初期化リスト部分のインデント数。
ContinuationIndentWidth
継続する行のインデント数。
Cpp11BracedListStyle
{}
の扱いを C++11 に適した方法にするかどうか。
true
に設定すると、波括弧{}
の端にスペースが入らない、閉じカッコ}
の前に改行を入れないなどの違いが有ります。
DerivePointerAlignment
true
に設定すると、*
や &
の位置を元にフォーマットされます。PointerAlignment
はフォールバックとしてのみ使用されます。
DisableFormat
フォーマット設定がすべて無視されるようになります。
ForEachMacros
関数呼び出しではなく、foreachループのためのマクロと認識させるもの。
たとえば FOREACH
というマクロを定義した時にそのままでは、関数呼び出しとみなされてしまうようです。
FOREACH(int x, ar) { std::cout << x << std::endl; }
ForEachMacros
として [FOREACH]
などを登録しておくと、
FOREACH (int x, ar)
{
std::cout << x << std::endl;
}
のようにフォーマットされるようになります。BOOST_FOREACH
なども登録しておくと良さそうです。
IndentCaseLabels
switch文の中の case
ラベルをインデントするかどうか。
false
に設定すると、
switch (x) { case A: break; case B: break; }
のようになり、true
だと
switch (x) { case A: break; case B: break; }
のようになります。
IndentWidth
インデント数。デフォルトのスタイルとしては 2
が多いみたいです。
IndentWrappedFunctionNames
関数の宣言か定義で、型名の後に改行された場合インデントするかどうか。
KeepEmptyLinesAtTheStartOfBlocks
ブロックの最初の空行を残すか否か。
MacroBlockBegin
ブロックを開始するマクロの正規表現。
MacroBlockEnd
ブロックを終了するマクロの正規表現。
MaxEmptyLinesToKeep
連続する空行の最大数。
NamespaceIndentation
namespace
でインデントするかどうか。
None
に設定すると、インデントされません。
namespace test { class A; } // namespace test
こんな感じ。All
に設定すると、
namespace test { class A; } // namespace test
このようにすべてインデントされます。
namespace test { namespace inner { class B; } // namespace inner class A; } // namespace test
Inner
と設定すると、他のnamespace
に内包された namespace
のもののみインデントされます。
PenaltyBreakBeforeFirstCallParameter
関数呼び出しで function_call(
のように改行する際のペナルティ。
PenaltyBreakComment
コメントを改行する際のペナルティ。
PenaltyBreakFirstLessLess
最初の <<
で改行する際のペナルティ。
PenaltyBreakString
文字列リテラルを改行する際のペナルティ。
PenaltyExcessCharacter
最大列数を超えた文字のペナルティ。
PenaltyReturnTypeOnItsOwnLine
関数の返り値の型を関数と同じ行に置くことのペナルティ。
PointerAlignment
ポインタや参照の *
、&
をどこに揃えるか。
Left
と設定した場合、int* a
のようになり、
Right
と設定した場合、int *a
となります。
Middle
だと、int * a
のようになります。
SpaceAfterCStyleCast
C言語スタイルのキャストの場合に、スペースを入れるかどうか。
true
にすると、(float) a
のように、変数とキャスト演算子の間にスペースが入ります。
false
の場合は (float)a
のようになります。
SpaceBeforeAssignmentOperators
代入演算子の前にスペースを入れるかどうか。false
とすると、
int a= 10
のように代入演算子の前にスペースが入らなくなります。
SpaceBeforeParens
開始カッコ (
の前にスペースを入れる方法について。
Never
とすると開始カッコの前にスペースが入りません。
ControlStatements
とすると、for/if/while などの後の開きカッコのみスペースが入ります。
Always
とすると、他のスタイルオプションやシンタックスルールで、禁止されていない限りスペースを入れるようになります。
SpaceInEmptyParentheses
空のカッコ ()
の中にスペースを入れるかどうか。
SpacesBeforeTrailingComments
1行コメント //
の前にスペースをいくつ入れるか。
SpacesInAngles
true
に設定すると、テンプレート引数の <
の後と >
の前にスペースが入ります。
SpacesInCStyleCastParentheses
C言語スタイルのキャストのカッコにスペースを入れるかどうか。
SpacesInParentheses
カッコ()
の内側にスペースを入れるかどうか。
SpacesInSquareBrackets
角カッコ []
の内側にスペースを入れるかどうか。配列アクセス [0]
などがそうです。
Standard
標準スタイルとして何を使用するか。
Cpp03
を指定すると、 C++03
に準拠した書き方に。Cpp11
と指定すると、C++11
に準拠した書き方に。
Auto
を指定すると、入力に応じて自動で適切なものが指定されます。
Cpp03
と Cpp11
の違いとしては、
C++03
では型の指定時に A<A<int>>
ではなく、 A<A<int> >
のように書かなければなりません。
これは、C++03
では、 >>
の部分が operator>>
に認識されてしまうからです。
C++11
ではこれは解決されているため、 A<A<int>>
のように書くことができます。
プロジェクト的には C++03 で書かないといけないのに、Cpp11
と指定されてしまうと、
折角のコードがフォーマッタを掛けた後にコンパイルエラーになる可能性もあるため、
適切なものに設定しましょう。
TabWidth
タブを使った時のタブ幅。
UseTab
タブを使うかどうか。
Never
を設定すると、タブは使用されません。
ForIndentation
を設定すると、インデント時のみ使用されます。
Always
で基本的にタブを使用するようになります。
ペナルティについて
設定項目としてペナルティという項目が有ります。
(PenaltyBreakBeforeFirstCallParameter
など)
ドキュメントを見てもペナルティに関してなにも書かれておらず、
stackoverflow でも同じような質問が上がっていました。
2013 European LLVM Conference にて、 Daniel Jasper さんが clang-format - Automatic formatting for C++ という題で詳しく発表されていました。 (スライドもビデオも公開されています)
発表内容によると、行を改行する際には、改行する方法によっていろいろな方法があるが、 その方法を評価するためにペナルティが使われており、そのペナルティが最小となるように 改行する位置が決められているようです。
stackoverflowであった回答では、
Namespaces::Are::Pervasive::SomeReallyVerySuperDuperLongFunctionName(args);
という関数があった場合、75文字となっているため、仮にColumnLimit
が 70
などに設定されていた場合、
デフォルトの設定ではPenaltyExcessCharacter
が非常に大きくとられているため、
ColumnLimit
を超える文字は基本的には改行されます。
Namespaces::Are::Pervasive::SomeReallyVerySuperDuperLongFunctionName( args);
つまり、一般的な設定では、ColumnLimit
を 70
にすると上のように改行されますが、
PenaltyExcessCharacter
を小さく設定した場合、
Namespaces::Are::Pervasive::SomeReallyVerySuperDuperLongFunctionName(args);
のように、ColumnLimit
を超えても改行されなくなります。
設定サンプル
各種設定を振り返ってみて、改めて設定を見なおしてみて設定しなおしてみた .clang-format
ファイルがこちらです。
--- | |
BasedOnStyle: 'Google' | |
AccessModifierOffset: -2 | |
AlignEscapedNewlinesLeft: true | |
AlignConsecutiveAssignments: false | |
AlignEscapedNewlinesLeft: true | |
AlignTrailingComments: true, | |
AllowShortBlocksOnASingleLine: true | |
AllowShortCaseLabelsOnASingleLine: false | |
AllowShortFunctionsOnASingleLine: 'Inline' | |
AllowShortIfStatementsOnASingleLine: false | |
AllowShortLoopsOnASingleLine: false | |
AlwaysBreakAfterDefinitionReturnType: false | |
AlwaysBreakBeforeMultilineStrings: true | |
AlwaysBreakTemplateDeclarations: true | |
BinPackArguments: true | |
BinPackParameters: true | |
BreakBeforeBinaryOperators: 'None' | |
BreakBeforeBraces: 'Allman' | |
BreakConstructorInitializersBeforeComma: true | |
ColumnLimit: 80 | |
ConstructorInitializerAllOnOneLineOrOnePerLine: true | |
ConstructorInitializerIndentWidth: 2 | |
ContinuationIndentWidth: 2 | |
Cpp11BracedListStyle: true | |
DerivePointerBinding: true | |
IndentCaseLabels: true | |
IndentWidth: 2 | |
IndentWrappedFunctionNames: false | |
KeepEmptyLinesAtTheStartOfBlocks: true | |
MaxEmptyLinesToKeep: 1 | |
NamespaceIndentation: 'None' | |
PointerAlignment: 'Left' | |
SpaceBeforeAssignmentOperators: true | |
SpaceBeforeParens: 'ControlStatements' | |
SpaceInEmptyParentheses: false | |
SpacesBeforeTrailingComments: 1 | |
SpacesInAngles: false | |
SpacesInCStyleCastParentheses: false | |
SpacesInParentheses: false | |
Standard: 'Cpp11' | |
TabWidth: 4 | |
UseTab: 'Never' |
clang-format いい感じに設定すると、フォーマットのことを一旦気にせずにコードがかけるので 結構満足度が高いです。 いろいろ設定を試してみて、自分のお気に入りの設定を見つけてみてください〜