はじめに
C++ Core GuidelinesとGuidelines Support Library(GSL)を読んで最新のC++について勉強しているところです。ガイドラインにおいてGSLの積極的利用が推奨されているので、GSLの使い方と共にメモっておきます。(随時更新)
C++ Core Guidelines: Abstract和訳
この文書はC++を上手く使うための指針の集合です。この文書は人々が最新のC++を有効に使うのを手助けすることを目的としています。ここで言う「最新のC++」とは、C++17、C++14、C++11を指しています。言い換えれば、あなたが今からコードを書き始めるとして、5年後にそのコードをどのように見えるようにしたいですか。10年後はどうですか。
この指針は、比較的高いレベルの論点、例えばインターフェース、リソース管理、メモリ管理、並行性、に焦点を合わせています。そのような指針はアプリケーションアーキテクチャとライブラリデザインに影響します。この指針に従えば、コードは静的に型安全で、リソースリークもなく、現在一般的に捉えられるものよりもおおくのプログラミングの論理的エラーを捉えることができるようになります。しかも速く動きます。―(この指針に沿って)物事を正しく行っても問題ないのです。
私達は低レベルの論点、例えば命名規則と字下げ形式、についてはそこまで関心を持っていません。しかし、プログラマーの助けとならないトピックは対象外です。
最初の指針の集合は(様々な形の)安全性と簡潔性について強調しています。それらは厳しすぎるかもしれません。私達は現実世界での必要性により良く対応させるためにもっと多くの例外を導入しなければならないと思っています。また私達はもっと多くの規則を必要としています。
いくつかの規則はあなたの期待やあるいは経験にさえ反しているかもしれません。もし私達があなたにコーディングスタイルを変えるよう示唆していなかったら、私達は間違っています。どうかこの指針を実証または反証してください。特に、我々は測定やより適切な例に基づいていくつかの指針を支持したいと考えています。
いくつかの規則は明らかかあるいはささいなことであると思うかもしれません。どうか、指針の目的の一つが、経験の浅い人や異なる分野・(プログラミング)言語からきた人の(最新のC++の)素早い習得の手助けにあることを忘れないでください。
多くの指針は解析ツールによって補助されるようデザインされています。指針に対する違反は関連する指針への参照あるいはリンクとともに知らされるでしょう。私達はあなたがコードを書く前にこれらのルールを全て憶えているとは考えていません。これらの指針は人にも読むことができるツールの仕様書として考えることができます。
これらの指針はコードベースへの段階的な導入を意図されています。私達はそうするツールを作る予定であり、他の人たちもそうすることを期待しています。
改善のためのコメントや指摘はいつでも歓迎しています。私達は、私達の理解の深化と(C++)言語とそのライブラリの進歩に合わせて、この文書を修正・拡張していく予定です。
GSL
- GSL: Guideline Support Library by Microsoft
- GSL Lite: Guideline Support Library for C++98, C++11 up by martinmoene
の2つが利用可能なようです。MicrosoftのものはC++14が必要ですが、GSL LiteはC98にも対応しています。
以下ではMicrosoftのGSLの場合で説明します。
C++ Core Guidelines
Narrowing conversionを避ける
ES.46: Avoid lossy (narrowing, truncating) arithmetic conversions
Narrowing conversionを明示的に行う場合はnarrow_cast
を使い、narrowing conversionによって情報が失われたときに例外を投げる場合はnarrow
を使用する。
double d = 1.2;
int i = narrow_cast<int>(d); // doubleからintに変換され情報が失われてもよいことを明示
int i = narrow<int>(d); // doubleからintに変換されるときに情報が失われてはならないことを明示
narrow
によって情報が失われた場合、gsl::narrowing_error
が投げられる。
assert
よりもExpects
とEnsures
を使う
I.6: Prefer Expects() for expressing preconditions
I.8: Prefer Ensures() for expressing postconditions
前提条件を確認する場合はExpects
を、事後条件を確認する場合はEnsures
を使う。
以下はガイドラインで示されている例:
int area(int height, int width)
{
Expects(height > 0 && width > 0);
// ...
}
void f()
{
char buffer[MAX];
// ...
memset(buffer, 0, MAX);
Ensures(buffer[0] == 0);
}
どちらの場合も条件を満たさなければgsl::fail_fast
が投げられる。
Expects
とEnsures
の挙動はマクロ
-
GSL_TERMINATE_ON_CONTRACT_VIOLATION
:(基本的に)std::terminate
が呼ばれる -
GSL_THROW_ON_CONTRACT_VIOLATION
:gsl::fail_fast
が投げられる -
GSL_UNENFORCED_ON_CONTRACT_VIOLATION
:コンパイラに条件を満たすとして最適化するよう指定する。
によって制御できる。
ちなみにCMake 3.12以降を使っていればadd_compile_definitions
を使って簡単にマクロを定義できるので便利。
span
やat
を使用する
ES.42: Keep use of pointers simple and straightforward
SL.con.3: Avoid bounds errors
std::vector
やstd::array
の[]
演算子は境界の確認を行わないので使わないこと。
代わりに[]
演算子においても境界の確認が行われるgsl::span
や
- 範囲
for
- メンバー関数
at()
- フリー関数
gsl::at()
を使って要素へアクセスする。
あるいはstd::vector
のラッパークラスを作り、[]
演算子の実装においてExpects
を使って境界確認を行ってもよい。
管理しなければならなくなる割に価値が低いので、gsl::at()
を使うべきでしょう。
現在研究用に作成しているライブラリでは、インデックスを使ってアクセスする場合は全てgsl::at
()を使うようにしました。こうするとエラーメッセージが統一されるのでわかりやすく、またマクロ一つで簡単に最適化ができるため非常に便利です。
span
ES.42: Keep use of pointers simple and straightforward
固定長配列や動的配列へ安全にアクセスすることが可能となり、またSTLコンテナ同様のメンバ関数・演算子が利用できる。
using namespace std;
using namespace gsl;
void f(span<int> s) {
for (auto&& e : s)
cout << e << endl;
}
int main() {
{
int arr[4] = {1, 2, 3, 4};
span<int> s = arr; // 固定長配列を参照するspan
f(arr); // arrからspanが自動的に生成される
span<int> s2 = {&arr[0], 2}; // arrの先頭から大きさ2の配列へのspan = {arr[0], arr[1]}
span<int> s3 = {&arr[0], &arr[2]}; // 上と同じだが{first, last}のペアでspanを作成
}
{
array<int, 4> arr = {1, 2, 3, 4};
span<int> s2 = arr; // arrayを参照するspan
}
}
他のライブラリと配列をやり取りしなければならないときにインターフェースとして重宝しています。
null
であってはならないポインタにはnot_null
を使う
I.12: Declare a pointer that must not be null as not_null
以下はガイドラインで示されている例:
int length(const char* p); // 悪い: pとしてnullptrを渡していいか不明
int length(not_null<const char*> p); // ベター: pがnullでないと仮定できる
int length(const char* p); // not_nullが使われていない場合、pがnullの可能性を必ず考慮する