CppCon 2014でHerb Sutter氏が,現代的なC++プログラミングの基本的なイディオムに関する講演を行った。ここではその要約を紹介しよう。
氏も認めているように,C++は難しい言語である。しかしながら,すべてのプログラマにとって複雑ということではない。低レベルなコードやライブラリを扱う時のような,言語の最も難解な部分について考慮すべきなのは,ごく限られた少数のプログラマであって,他はそれを単に動作する"デフォルト"として使用することが可能なはずだ,と氏は言う。
代表的な例のひとつは 範囲for
ループの利用だ。次のような記述も可能だが,
for (auto i = begin(c); i != end(c); ++i) { ... }
最近のC++であれば,同じことをもっと簡単に
for (auto& e : c) { ... }
と記述できて,可読性も優れている。
ポインタ,参照,newとdelete
もうひとつ,氏が強く推奨するのは,低レベルのデータ構造内にカプセル化されるものを除いて,ポインタ変数*
の所有やnew
,delete
を使用しない,ということだ。unique_ptr
を使用するか,共有が必要と分かっていればshared_ptr
を用いるのが望ましい方法だと氏は言う。次のように簡単に記述すればよい。
auto p = make_unique<widget>(); auto q = make_shared<widget>();
make_unique
とmake_shared
を使えば,deleteについて気にする必要もなくなる。
しかしながら,*
と&
がなければリソースの使用量が大きくなる。この2つの最適なユースケースは,関数に渡すパラメータだ。本当のアンチパターンだと氏が考えるのは,パラメータ渡しに変数の参照カウントを使用することである。これはただ単にパフォーマンス問題の原因となるだけであり,ラッパオブジェクトの所有を譲渡する目的でのみ使用されるべきものだ。
ローカル変数の宣言にauto
を使用する
auto
キーワードは,現代的なC++の機能として最も重要なもののひとつだ,と氏は言う。auto
は変数型を推論するためだけでなく,使用される型を特定するためにも使用することができる。次のような文を考えてみよう。
auto i = v.begin();
これによって,使用されている型が正確に推論される。次のような記述よりもはるかに簡単で,難しいことを考える必要もない。
vector::const_iterator i = v.begin();
ここでは特に,const_iterator
の利用も考慮しなくてはならない。
このような正確さがauto
の最大のメリットだが,その価値は他の面にもある。
-
メンテナンス性: 関数の戻り値など,変更が容易に適応できるコードになる。
-
パフォーマンス: 正確な推論の帰結として,オブジェクトの初期化時などで,一時的オブジェクトが不用意に生成されないことも保証できる。
-
ユーザビリティ: ラムダのような特殊なコンテキストにおいて,
auto
は不可欠なファクタである。 -
型利便性(Type Convenience): いくつかのキーワードを省略できる。
ただしC言語の配列を扱う場合や,移動が不能あるいは容易でない型など,auto
を使用することのできないケースもいくつかある。
auto lock = lock_guard<mutext> { m }; // エラー: 移動不可 auto ai = atomic<int>{}; // エラー: 移動不可 auto a = array<int,50>{}; // 正しいがコストが高い
パラメータの受け渡し
氏の講演では,パラメータの受け渡しの説明に多くの時間を割いていた。ここでのおもなポイントは,C++98のデフォルトが現在のC++でも引き続き有効であり,その出発点でもある一方で,右辺値の最適化によっていくつの機能が追加されているということだ。一例として,常に行うべきことは,
class employee { std::string name_; public: void set_name(const std::string& name) { name_ = name; } // C++98の標準的な記述方法 void set_name(std::string&& name) noexcept { name_ = std::move(name); } // 右辺値の最適化 };
その一方で氏は,値渡しは主としてコンストラクタで使用するように提案する。それ以外での値渡しは,右辺の最適化が可能ではあるが,名前付きのパラメータで渡す場合(つまり一時的オブジェクト)のパフォーマンス損失が大きくなる可能性があるからだ。
タプル
最後の提案は,戻り値としてのタプルの利用に関するものだ。
tie(iter, success) = myset.insert("Hello"); if (success) do_something_with(iter);
デフォルトやイディオムの強調に加えて,氏は,過度に難解なソリューションに到達することの多い,過度な考慮を避けるように述べている。すべての点において氏の提案は,プログラム言語がもっとも基本的なレベルで提供する機能の利用を原則とした上で,地道な試行錯誤によるパフォーマンス向上の労力に見合った成果が期待できる領域を慎重に選び出しているのだ。