C++コードのデトックス


前振

syoyoさんの C++ コードを内面からキレイに保つ5つのレシピ という記事に触発されて、私もコードを綺麗に保つためのデトックス・テクニックを紹介したいと思います。

効くか効かないかは個別のプロジェクトの体質しだいです

Compiler

まず最初のテクニックはコンパイラの活用です。コンパイル警告を0に保ちましょう。このテクニックの元はMetting C++ 2014の以下の発表です。

Clangではオススメのコンパイル・オプションは以下の通りです。

-Werror -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic

GCCではオプションのリストはとても長くなります。。。StackOverflowの記事"Flags to enable thorough and verbose g++ warnings@StackOverflow" などを参考にされると良いかと思います。以下、推奨オプションの抜粋。

-pedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -Wmissing-declarations -Wmissing-include-dirs -Wnoexcept -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-conversion -Wsign-promo -Wstrict-null-sentinel -Wstrict-overflow=5 -Wswitch-default -Wundef -Werror -Wno-unused

Visual C++では単純に /W4 です。

cmakeを使ってビルドする場合は、以下のようにGenerator Expressionsを使用してコンパイラ毎にオプションを切り替えると便利です。

project(detox)

cmake_minimum_required(VERSION 3.3)

add_executable(detox main.cpp)
target_compile_options(
  detox
  PRIVATE
  $<$<CXX_COMPILER_ID:Clang>:-Wall -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic>
  $<$<CXX_COMPILER_ID:GCC>:-pedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -Wmissing-declarations -Wmissing-include-dirs -Wnoexcept -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-conversion -Wsign-promo -Wstrict-null-sentinel -Wstrict-overflow=5 -Wswitch-default -Wundef -Werror -Wno-unused>
  $<$<CXX_COMPILER_ID:MSVC>:/W4>
  )

警告レベル最大をベースにして自身の環境に合わせ不要な警告をオフにしていきましょう。(-Wno-c++98-compatみたいにです)

既存のコード・ベースにこの手法を適用すると警告数に圧倒されるかもしれません。その場合は時間を見つけて少しづつ警告数を減らして行きましょう。また、3rd Party OSSも警告が大量にでるケースがあります。その場合は警告を修正してOSSに貢献しましょう(^^)

Include-What-You-Use

次のテクニックはinclude関係の整理です。Include-What-You-Useを使用すると、不要なヘッダ・インクルード、前方宣言での置換方法を教えてくれます。

CMake v3.3からInclude-What-You-Useの使用をサポートするようになりました。そのためCMakeLists.txtで 変数CMAKE_CXX_WHAT_YOU_USEにコマンドinclude-what-you-useを指定すると、ビルド時に Include-What-You-Useの結果も出力されるようになります。

Include-What-You-Useを使用するとビルド時間が増加するので、有効・無効を選択できるようにしています。 以下のようにCMakeLists.txtを作成して、ビルド・プロジェクト生成時に-DENABLE_INCLUDE_WHAT_YOU_USE=ONで Include-What-You-Useを有効にできます。

project(detox)

cmake_minimum_required(VERSION 3.3)

option(ENABLE_INCLUDE_WHAT_YOU_USE
  "Enable include-what-you-use. Use include-what-you-use to sanitize header-include dependencies.\
Using include-what-you-use can slow the compilation, so if the compilation is slow turn this off."
  ON)
if(ENABLE_INCLUDE_WHAT_YOU_USE)
  find_program(INCLUDE_WHAT_YOU_USE include-what-you-use)
    if(INCLUDE_WHAT_YOU_USE)
      message(STATUS "Enable Include What You Use")
      set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE include-what-you-use)
    else()
      message(WARNING "Could not find includ-what-you-use")
    endif()
endif()

add_executable(detox main.cpp)

現状CMakeのInlucde-What-You-UseサポートはGeneratorがMakefileかNinjaの時だけ有効になるそうです。

<LANG>_INCLUDE_WHAT_YOU_USE Propertyの説明@CMake Manual

ヘッダのinclude依存が複雑だと再ビルド時に関係のないファイルのコンパイルが必要になります。大きなプロジェクトになるとビルド時間に深刻な問題を引き起します。日頃から整理しておくと良いかと思います。

Clang-Format

Clang-Formatはコードの整形を行ってくれるツールです。Clang-Formatの使い方の説明は省略しますが、以下の説明ではプロジェクトのトップ・ディレクトリに設定ファイル.clang-formatがあり、コマンドclang-formatが プロジェクトのトップ・ディレクトリで実行できる状態であると仮定します。

.clang-formatをinteractiveに生成するツールを利用すると、自身の好みにあった設定ファイルが作りやすいかと思います。

今回はCMakeのカスタム・ターゲットとしてビルド・プロセスにClang-Formatを組込みます。プロジェクト中のソース・ファイルに clang-formatを適用するカスタム・ターゲットformat-with-clang-formatを定義します。そしてadd_dependenciesで、 format対象のターゲットdetoxがformat-with-clang-formatへ依存するようにしています。こうすることで、detox(Exe)のビルドの前に、clang-formatでコードが整形されます。ビルド前に整形することで、clang-formatがビルドを壊さないか確認できます。

project(detox)

cmake_minimum_required(VERSION 3.3)

add_executable(
  detox 
  main.cpp
  )

option(FORMAT_FILES_WITH_CLANG_FORMAT_BEFORE_EACH_BUILD
  "If the command clang-format is avilable, format source files before each build.\
Turn this off if the build time is too slow."
  ON)
find_program(CLANG_FORMAT clang-format)
if(CLANG_FORMAT)
  message(STATUS "Enable Clang-Format")
  get_target_property(MY_SOURCES detox SOURCES)
  add_custom_target(
    format-with-clang-format
    COMMAND clang-format -i -style=file ${MY_SOURCES}
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
    )
  if(FORMAT_FILES_WITH_CLANG_FORMAT_BEFORE_EACH_BUILD)
    add_dependencies(detox format-with-clang-format)
  endif()
endif()

プロジェクトが大きくなるとビルドの前に毎回clang-formatを実行するのは時間的に厳しくなるかもしれません。その時はフラグFORMAT_FILES_WITH_CLANG_FORMAT_BEFOR_EEACH_BUILDをOFFに設定します。OFFにした場合、 clang-formatは自動的に実行されません。ユーザがカスタム・ターゲットformat-with-clang-formatをビルドする必要があります。 コマンド・ラインでは以下のようにビルドできます。

cmake --build path_to_build_dir --target format-with-clang-format

最後に(継続)

この記事では3つのツール(Compiler Warnings & Include-What-You-Use & Clang-Format)と、それらのツールをCMakeを通してビルド・プロセスに組込む方法を紹介しました。

C++のコード・ベースを綺麗に保つための一番の秘訣はツールそのものではなく、The Boy Scout Ruleのように継続した改善活動です。普段のビルド・プロセスに対して、これらのツールをどれだけシームレスに組込めるかが継続するためのキーかと思います。改善するためのステップが難しければ面倒で誰も行いません。

皆さんもC++のコード・ベースを綺麗に保つため、ビルド・プロセスを一工夫してみるのは如何でしょうか。C++ コードを内面からキレイに保つ5つのレシピで紹介されているツールを組込むのも素晴らしいかと思います。

テスト・プロセスもcmakeのツール群を使うとかなり強力になりそう(結果の集計とか)なのですが、まだ試行錯誤中です。 CTest/CDashあたりの知見を持っているかたは是非教えてください!

あと今回のサンプル・コードのレポジトリです。