def yasuharu519(self):

日々の妄想

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-formatvimに導入することで vimからも使えます。

github.com

にスタイルの内容がまとまっているので、そちらを参考するのもよさそう。 今回は、設定するに当たって、試してみないとわからない部分も多かったため、 一つ一つ試しながら挙動確認を行いました。 それぞれのスタイルのより詳細な説明を知りたい方は、上記のドキュメントを参照してもらうのが良さそうです。

インストール

$ 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 の値がすでにインデントされるので、その位置からのインデント。IndentWIdth2AccessModifierOffset0 だと、

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;
}

こんな風に、波括弧が前の行にくっついてます。

LinuxAttach と似ていますが、関数や名前空間、クラス定義の前は改行します。

MozillaAttach と似ていますが、 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 を指定すると、入力に応じて自動で適切なものが指定されます。

Cpp03Cpp11 の違いとしては、 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 でも同じような質問が上がっていました。

stackoverflow.com

2013 European LLVM Conference にて、 Daniel Jasper さんが clang-format - Automatic formatting for C++ という題で詳しく発表されていました。 (スライドもビデオも公開されています)

発表内容によると、行を改行する際には、改行する方法によっていろいろな方法があるが、 その方法を評価するためにペナルティが使われており、そのペナルティが最小となるように 改行する位置が決められているようです。

stackoverflowであった回答では、

Namespaces::Are::Pervasive::SomeReallyVerySuperDuperLongFunctionName(args);

という関数があった場合、75文字となっているため、仮にColumnLimit70 などに設定されていた場合、 デフォルトの設定ではPenaltyExcessCharacter が非常に大きくとられているため、 ColumnLimit を超える文字は基本的には改行されます。

Namespaces::Are::Pervasive::SomeReallyVerySuperDuperLongFunctionName(
  args);

つまり、一般的な設定では、ColumnLimit70 にすると上のように改行されますが、 PenaltyExcessCharacter を小さく設定した場合、

Namespaces::Are::Pervasive::SomeReallyVerySuperDuperLongFunctionName(args);

のように、ColumnLimit を超えても改行されなくなります。

設定サンプル

各種設定を振り返ってみて、改めて設定を見なおしてみて設定しなおしてみた .clang-format ファイルがこちらです。

clang-format いい感じに設定すると、フォーマットのことを一旦気にせずにコードがかけるので 結構満足度が高いです。 いろいろ設定を試してみて、自分のお気に入りの設定を見つけてみてください〜