2015年9月25日金曜日

C++ Core Guidelinesの紹介

C++コミュニティ最大の祭典CPPCON 2015が2015-09-21(Mon)から開催されています。基調講演"Writing Good C++14"でBejarne StroustrupがModern C++と呼ばれるC++11以降の標準で良いコードを書くためのガイドライ ン"C++ Core Guidelines"を発表しました。またガイドラインをサポートするための小さなヘッダ・オンリー・ ライブラリ"Guidelines Support Library"をMicrosoftが公開しました。

今回は、C++ Core Guidelinesの概要とGuidelines Support Libraryを利用したコードがどのようになるのか 簡単に説明したいと思います。

C++ Core Guidelinesは抽象度の高いPhilosophyや、より具体的なClass/Functionsの設計方法など多岐に渡る アドバイスを含んでいます。各アドバイスは"ルール"、"ルールを設定する理由"、"ルールの具体例"など決め られた項目から構成されています。

1つ具体的なアドバイスの例を上げてみます。以下は、Interfaceに関するアドバイスの1つです。"//"(ダブル・ スラッシュ)以降は私のコメント。

// ↓ルール
I.1: Make interfaces explicit

// ↓ルールの理由。
Reason: Correctness. Assumptions not stated in an interface are easily overlooked and hard to test.

// ↓ルールの具体例。ここではルールに沿っていない悪い例(bad)。
Example, bad: Controlling the behavior of a function through a global (namespace scope) variable (a
call mode) is implicit and potentially confusing. For example,

int rnd(double d)
{
    return (rnd_up) ? ceil(d) : d;  // don't: "invisible" dependency
}
It will not be obvious to a caller that the meaning of two calls of rnd(7.2) might give different results.

// ルールに対する例外(適用しなくても良い条件)。
Exception: Sometimes we control the details of a set of operations by an environment variable, e.g.,
normal vs. verbose output or debug vs. optimized. The use of a non-local control is potentially
confusing, but controls only implementation details of an otherwise fixed semantics.

// ↓悪い例。
Example, bad: Reporting through non-local variables (e.g., errno) is easily ignored. For example:

fprintf(connection,"logging: %d %d %d\n",x,y,s); // don't: no test of printf's return value
What if the connection goes down so than no logging output is produced? See Rule I.??.

// ↓悪い例の改善方法。
Alternative: Throw an exception. An exception cannot be ignored.

Alternative formulation: Avoid passing information across an interface through non-local state. Note
that non-const member functions pass information to other member functions thorough their object's
state.

Alternative formulation: An interface should be a function or a set of functions. Functions can be
template functions and sets of functions can be classes or class templates.

// ↓ルールをどのように守るか。
Enforcement:

(Simple) A function should not make control-flow decisions based on the values of variables declared at namespace scope.
(Simple) A function should not write to variables declared at namespace scope.

このようなアドバイスが2015-9-24(Thu)の時点で200個程あります。文量が多いので1度に全てを理解すること は難しいと思います。またC++ Core Guidelinesは、C++コミュニティが参加して頻繁に更新されます。時間が あるときに少しづつ読み進め、適用できるアドバイスから自身のコード・ベースに反映していくのが良いかと 思います。

続いてMicrosoftが提供しているGuidelines Support Libraryを利用したサンプル・コードを見てみようと思 います。Guidelines Support Libraryはヘッダ・オンリー・ライブラリなのでgithubからクローンして、イン クルード・パスを設定するだけで使用することができます。clangではC++14を要求するようです。もし、 Guidelines Support Libraryがコンパイルできない古いコンパイラを使っている場合は良い機会なので最新の コンパイラを導入しましょう。

git clone https://github.com/Microsoft/GSL.git
g++ -I path_to_GSL/include -std=c++14 my_source_code.cpp

array_viewはCスタイル配列を取るIFを改善するためのクラス・テンプレートです。以下 ガイド・ラインに沿っていない古き良き時代のCのコードです。array decayや配列サイズのミス・マッチなど バグを埋め込みやすいコードになってしまいます。

#include <cassert>

// 配列をポインタとして扱うarray decayが起きる。
int bad_sum(int* arr, size_t n){
  int sum = 0;
  for(auto i = 0; i < n; ++i){
    sum += arr[i];
  }

  return sum;
}

int main()
{
  int arr[4] = {0, 1, 2, 3};
  assert(6 == bad_sum(arr, 4));
  assert(6 == bad_sum(arr, 5));// 配列のサイズがあっていないのでバグ(範囲外アクセス)。
  return 0;
}

GSLのarray_viewを利用してGuidelinesに従ったコードにします。array_viewを使用するとプログラマが明示 的に配列サイズを書くのは配列の初期化時のみとなります。また、サイズ以上の領域にアクセスしようとする と、assertionでプログラムが落ちるためバグを早く見つけることができます。また、array_viewを使用した IFは、配列中の特定の範囲へのアクセスやvectorでも使用できます。

#include <cassert>
#include <vector>
#include <array_view.h> // from GSL

int good_sum(Guide::array_view<int> arr){
  // arr[7]; // 範囲外のアクセスはassertionで落る。
  int sum = 0;
  for(auto x : arr){ // range for-loopが使える。
    sum += x;
  }
  return sum;
}

int main()
{
  int arr[4] = {0, 1, 2, 3};
  assert(6 == good_sum(arr)); // サイズ情報はプログラマが渡す必要はない。
  assert(3 == good_sum({arr, 3})); // サブ範囲アクセス。
  std::vector<int> vec = {4, 5, 6, 7, 8};
  assert(30 == good_sum(vec));
  return 0;
}

array_viewの他にもstring_viewはownerテンプレートなど、C++ Core Guidelinesをサポートするための便利 なツールが定義されています。GSL自体はとても小さく、3ファイルから構成さているので、どのようなツール が用意されているか、ぜひ目を通してみてください。

また、Herb SutterのセッションはStroustrupの基調講演の続きでVisual Studioによる静的解析ツールの紹介 が行なわれています。Guidelines Support Libraryを利用してC++ Core Guidelinesに従ったコーディングを することで静的解析ツールの力を借りて、C++の積年の課題であったDangling PointerやBounds Errorの問題 を駆逐して、Type & Memory Safetyを実現できるようです。

Stroustrupが講演の中でも触れていますが、新しいツール(C++11/14,Guidelines Support Library、新しい Visual Studio/GCC/Clang)と適切なガイド(C++ Core Guidelines)があれば高い生産性と品質の高さ(性能、バ グの少なさ、etc)は両立可能です。C++の研修・コースと称してCやJavaを教えるのではなく、C++ Core Guidelinesに従って最新のツールを利用しながらC++の教育を行えば組織の競争力を高くできそうですが、日 本ではそういった企業はあるのでしょうか。

今年のCPPCONは動画の公開がとても早くBejarne StroustrupやHerb Sutterの基調講演は既にYoutubeで公開さ れています。ぜひ見てください。

Author: Mitsutaka Takeda

Created: 2015-09-25 Fri 16:41

Validate