中規模なC++の新しいプロジェクトを作るときにやるべきこと 2016年版


C++で新しいプロジェクトを作成するときに自分が定型的にやっていることを備忘録的にまとめました。完全に我流なので「こういうやり方もあるよ」などのアドバイスは歓迎です。

この記事中では各ファイルで説明が必要な部分だけを示しますが、全てのサンプルはgithubにおいてあります。

cpp-template: https://github.com/m-mizutani/cpp-template

前提

  • UNIXベースのCLIで動作するアプリケーション or ライブラリを作る
  • コンパイルはcmakeを使用する
  • ファイル名はソースコードが*.cc、ヘッダファイルが*.hppだとする
  • サンプルではcpptemplate というプロジェクト名だと仮定します

プロジェクト用ファイルの準備

CMakeList.txt

サンプル

cmakeでビルドするための設定ファイルです。このサンプルではCLIアプリケーションを作る場合でも、単体テストを簡単にするため共有ライブラリを作成するようにしています。

# Library
ADD_LIBRARY(cpptemplate SHARED ${BASESRCS})
IF(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
    TARGET_LINK_LIBRARIES(cpptemplate pcap pthread rt)
ELSE(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
    TARGET_LINK_LIBRARIES(cpptemplate pcap pthread)
ENDIF(${CMAKE_SYSTEM_NAME} MATCHES "Linux")

自分はよくlibpcapおよびpthreadを利用したコードを書くので、上記部分で自分が作成するライブラリ以外にリンクするライブラリを記述しています。読めばだいたいわかると思いますが、最初のIF節以下がLinuxの場合、後のELSE節以下がLinux以外の場合(主にOSXを想定)と分けて記述できます。

src/*

ライブラリを構成するソースコードをsrc以下に格納しています。ここではライブラリのメインとなるロジックを書くcpptemplate.cc、そのヘッダフィアルであるcpptemplate.hpp、そして独自のexceptionを書くcpptemlate/exception.hppという構成になっています。これはあくまで最小限の構成なので、必要に応じてファイルなどを追加します。

CMakeList.txtのサンプルに従うと、src/cpptemplate/ 以下をincludeディレクトリ(例えば/usr/local/include)にまるっとコピーするので、複数ヘッダファイルにわけて管理したい場合などはsrc/cpptemplate/の下に作って、以下のようにインクルードしておくと、外部から利用する別のソフトウェアはcpptemplate.hppだけを読みこめば必要なヘッダファイルが一括して読み込まれるようになります。

cpptemplate.hpp
()
#include "./cpptemplate/exception.hpp"
()

test/*

作成したライブラリを単体テストするためのコードを置くディレクトリです。GoogleTestを利用しています。

gtest*ファイルはGoogleTestからそのまま拝借したものになります。この3つのファイルはプロジェクト作成後にはいじらない想定です。

ではどうやってテストを追加するのかというと、以下の様なC++ソースコードをtest以下に置いて、cmake .を再度実行すると勝手にtest以下の*.ccファイルをglobしてコンパイル・リンクし、ビルドされる./bin/cpptemplate-testを実行するとテストが実行されます。

MyTest.cc
#include "./gtest.h"
#include "../src/cpptemplate.hpp"

TEST (MyTest, test1) {
  int a = 4;
  int b = 3;
  EXPECT_EQ(7, a + b);
}

ライブラリ自体はCMakeList.txtでリンクするように記述していますが、ヘッダファイルは必要に応じて読み込ませる必要があります。

GoogleTestの書き方は以下を参照してください。

cli/*

CLIのアプリケーションを作成する場合、ここでコマンドラインのパースやライブラリの読み込みを実施します。ここであまりロジックを持たないように書くことで、ライブラリ側にロジックを寄せ、テストが記述しやすくなります。

ライブラリのみの開発の場合、このソースコードの記述は不要です。

README.md, LICENSE.md

書きましょう。特にこのサンプルに従う場合、GoogleTestについては自分のライセンス外となるので、記述に注意が必要です。

.gitignore

サンプルでは、CMake利用時に自動生成されるファイルを記載しています。

ファイルの準備完了

最終的に、以下の様なファイル構成になります。

% find . -type f | grep -v ".git"
./cli/main.cc
./CMakeLists.txt
./LICENSE.md
./README.md
./src/cpptemplate/exception.hpp
./src/cpptemplate.cc
./src/cpptemplate.hpp
./test/gtest-all.cc
./test/gtest.h
./test/main.cc

また、ここまでのファイルの作成とcpptemplateから自分のプロジェクト名へのrenameを、サンプルのレポジトリに同梱されているcopy.pyによって実行できます。

$ ./copy.py mynewproj

のように実行すると、mynewprojというディレクトリが作成され、その中にファイル名やコード内の名前がcpptemplateからmynewprojに変更されたファイル群がコピーされます。(ハイフンやアンダーバーの扱いが面倒だったので、そのあたりはあまり考慮してません。。)

ビルド

正しくファイルが生成されていれば、以下の手順でビルドされます。以下、OS X 10.11.6での実行結果です。

$ cmake .
-- The C compiler identification is AppleClang 7.3.0.7030031
-- The CXX compiler identification is AppleClang 7.3.0.7030031
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc
-- Check for working C compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- No build type selected, default to Release
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/mizutani/Dropbox/works/cpp-template
$ make
Scanning dependencies of target cpptemplate
[ 14%] Building CXX object CMakeFiles/cpptemplate.dir/src/cpptemplate.cc.o
[ 28%] Linking CXX shared library lib/libcpptemplate.dylib
[ 28%] Built target cpptemplate
Scanning dependencies of target cpptemplate-bin
[ 42%] Building CXX object CMakeFiles/cpptemplate-bin.dir/cli/main.cc.o
[ 57%] Linking CXX executable bin/cpptemplate
[ 57%] Built target cpptemplate-bin
Scanning dependencies of target cpptemplate-test
[ 71%] Building CXX object CMakeFiles/cpptemplate-test.dir/test/gtest-all.cc.o
[ 85%] Building CXX object CMakeFiles/cpptemplate-test.dir/test/main.cc.o
[100%] Linking CXX executable bin/cpptemplate-test
[100%] Built target cpptemplate-test
$ ./bin/cpptemplate-test
[==========] Running 0 tests from 0 test cases.
[==========] 0 tests from 0 test cases ran. (0 ms total)
[  PASSED  ] 0 tests.
$ ./bin/cpptemplate
Hello, Hello, Hello!

テストは何も入っていないので0 testsとなっていますが、追加すればここが増えていきます。

インストール

コンパイルができていればあとはそのまま自分のシステムへのインストールは可能です。場合によってsudoコマンドなどによる特権が必要です。

$ make install
[ 28%] Built target cpptemplate
[ 57%] Built target cpptemplate-bin
[100%] Built target cpptemplate-test
Install the project...
-- Install configuration: "Release"
-- Up-to-date: /usr/local/lib/libcpptemplate.dylib
-- Installing: /usr/local/include/cpptemplate.hpp
-- Installing: /usr/local/include/cpptemplate/cpptemplate.hpp
-- Installing: /usr/local/include/cpptemplate/exception.hpp
-- Installing: /usr/local/bin/cpptemplate

開発サポートツールの利用

Travis CI

いわゆるIntegrationテストが必要なケースはあまりないかもしれませんが、複数環境でのテストを自動で実施してくれるのはとてもありがたい存在です。特にC/C++などの場合、同じUNIX系でもOSが違うとコンパイルが通ったり通らなかったりということがあるので、commitのたびに一通り確認してくれると安心感があります。

Travis CIそのものの設定方法などは別サイトなどを参照してもらうとして、C++のプロジェクトに必要な点だけここで解説します。

Travis CIを利用する場合はレポジトリのトップに.travis.ymlというファイルを設置します。

travis.yml
language: cpp

compiler:
  - clang
  - gcc

os:
  - osx
  - linux

上記のように記述することで、まずC++プロジェクトであることを宣言し、コンパイラとOSを選択できます。ここで選んだコンパイラとOSの全組み合わせ(といってもこの例では4パターンだけですが)をテストしてくれます。

travis.yml
before_install:
  # update packages
  - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get update -qq -y; fi
  - if [ "$TRAVIS_OS_NAME" == "linux" ]; then sudo apt-get install -y build-essential cmake libtool libpcap0.8 libpcap-dev ; fi
#  - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew update ; fi
#  - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew install msgpack libev ; fi

ここで事前に必要なパッケージなどのインストール用コマンドを記述します。Travis CIはあまりC/C++などの利用を想定していないため、例えばLinuxの場合、ライブラリなどはaptを叩いて自前でインストールさせる必要があります。特にLinuxは素の状態だとコンパイルに必要なツール類も入ってないので、build-essentialcmakeも忘れずにインストールします。

$TRAVIS_OS_NAMEにOS名がセットされているので、それによって地道に処理の分岐を記述します。

travis.yml
before_script:  
  - cmake .

script:
  - make

最後に上記のようにコンパイルを実行します。もしテストコマンドも実行したほうがよければ、scriptの項目に追記する形にします。

script:
  - make
  - ./bin/cpptemplate-test

cpplint

コーディング規約に従っているかをチェックしてくれるツールのC++版です。筆者はGoogleが提供しているcpplint.pyを利用しています。Googleが提供しているため、GoogleのC++スタイルガイドに従ったものになります。必ずしもGoogleのスタイルガイドが最も良いわけではないので、必要に応じて変更などをするのが良いと思います。

cpplint.py: https://github.com/google/styleguide/blob/gh-pages/cpplint/cpplint.py

cpplint.pyは単体で動くファイルなので、個別にダウンロードして自分のプロジェクトディレクトリに入れておくのでもよいかと思います。

今回のようなプロジェクトであれば以下のコマンドでチェックをしてくれます。(レポジトリのトップディレクトリで実行するとします)

$ ./cpplint.py --extensions=hpp,cc src/**/*.hpp src/**.cc src/**.hpp

もしcommit前にこのチェックを実行し、全てパスしないとcommitできないようにするためには.git内に以下のファイルを用意します。

./cpplint.py --extensions=hpp,cc src/**/*.hpp src/**.cc src/**.hpp

cpplint.pyのパスは自分の環境に合わせて変更してください。pre-commitスクリプトの実行時ディレクトリは、レポジトリのトップディレクトリになります。

またEmacsで開発するのであれば、Google-c-style.elを導入するとインデントなどのスタイルが一致するので、おすすめです。package.elを利用しているのであれば、MELPAからgoogle-c-styleをインストールし、以下の設定を~/.emacs.d/init.elに記述すれば導入できます。

emacs.d/init.el
(add-hook 'c-mode-common-hook 'google-set-c-style)

完成

以上で新しいC++プロジェクトの用意が一通りそろいます。良いC++ codingライフを!