Modern C++ Programming
こんばんは.誰に投げるか決めてなかったために2日連続です,N@Nです.つらいです.また,C++の話です.
アブストラクト
一般に用いられるC++コンパイラ(gcc及びClang)は当然のことながら最新のC++にどんどんと対応していく.
最新のC++コンパイラのC++11,C++14対応に関しては以下を参照すると良いだろう.
コンパイラの実装状況 - cpprefjp - C++ Library Reference
C++1y/C++14 Support in GCC - GNU Project - Free Software Foundation (FSF)
Clang - C++1z, C++14, C++11 and C++98 Status
これを見る限りgccについてもClangについてもC++11の対応は完了したと言って良いだろう.但し,C++14対応はgccの方がClangに後れを取っている,と言った感じだ.
C++11によってC++は大幅に進化した.コンパイラの対応状況から言ってもC++11の機能は可能な限り使えるべきだろう.
C++11の全ての機能については書籍であるC++ポケットリファレンスや,Webサイトでもリファレンスがいくらでも転がっているため,ここでは特に主要な機能に焦点を当てたいと思う.
また,各機能においても詳細な議論をすると,それだけで一記事が埋まってしまうので詳細は他の記事を参照されたい.
ちなみに2014年12月19日現在コンパイラの最新バージョンは
- gcc 4.9.2 (2014/10/30 Released)
- Clang 3.5 (2014/09/04 Released)
だ.ところでMSVCは2015でかなりの対応を見せるとのことだったが果たしてどうなるのであろうか.
C++11
前述の通り,C++11の機能は最早完全に実装されたと言っても良いほどだ.
C++11の中でも特に用いることが多いであろう機能を以下に記そう.
型推論
いわゆる型推論がC++11より導入された(C++03以前にもautoというキーワードはあったが最早過去のものだ).
auto a = 0; // a => int auto b = 4.0; // b => double auto c; // ill-formed
また,gcc 4.9.0-において-std=gnu++1y
オプションを指定することにより,関数の引数にも型推論を使用することができる.
ヌルポインタ
これまでC++におけるNULLとは単なるint型の0であるマクロに過ぎなかった.
#define NULL 0
これがC++03までのNULLだ.
しかし,C++11で導入されたnullptrはnullptr_t型の実装となった.これによってオーバーロード時の不具合を避けることができる.
ところで/usr/local/include/c++/4.9.0/i686-pc-linux-gnu/bits/c++config.h
を見てみよう.
#if __cplusplus >= 201103L typedef decltype(nullptr) nullptr_t; #endif
とある.また,/usr/local/include/c++/4.9.0/ext/type_traits.h
には
#if __cplusplus >= 201103L inline bool __is_null_pointer(std::nullptr_t) { return true; } #endif
とある.
配列
実装に関しては昨日の記事でなんとか読もうとしたものがあるので,大した参考にならないとは思うが参照する方は参照されると良い.
C++03までの配列と違い,C++11のstd::arrayはstd::vectorなどのコンテナと同様のイテレータを持つ配列だ.
range based for
いわゆるforeachだ.std::vectorなどのコンテナに対し,煩わしいコードを書かずに済む.ループカウンタのような本来不要な変数をなくすことにもなる.
std::vector<int> vec = {0, 1, 2}; for(auto const& v : vec) { std::cout << v << " "; } // => 0 1 2
スマートポインタ
C++03までは,動的なメモリの確保にnew
,delete
を用いていた.うっかりdelete
し忘れることによってメモリリークを起こした方も多いのではないか.
また,new
をしていないポインタに対するdelete
や,delete
したポインタに対するdelete
は未定義であった.
スマートポインタはそれを解決する.
shared_ptr<type>
とunique_ptr<type>
についてのおおまかな違いは
shared_ptr<type>
はメモリの所有権を共有できるが,unique_ptr<type>
は唯一であるshared_ptr<type>
はコピーが可能であるが,unique_ptr<type>
は所有権の移動に限られる.
他,deleterの指定ができるなどの機能がある.
std::shared_ptr<int> sptr1; std::shared_ptr<int> sptr2; sptr2 = sptr1; // OK std::unique_ptr<int> uptr1; std::unique_ptr<int> uptr2; uptr2 = uptr1; // ill-formed
コンパイル時定数
constexpr宣言された変数はコンパイル時変数となる.
constは単なる定数であるオブジェクトであり,初期化子が定数式でなければ定数式にはならなかった.
しかし,constexprは宣言した変数を明示的にコンパイル時定数としてくれる.可能な限り定数にはconstexpr宣言をするべきだ.
// C++03 #define INF 10e5 // bad const int INF = 10e5; // good // C++11 constexpr int INF = 10e5;
ちなみにだが,gcc 4.8.2(恐らくはそれ以前もだ)において
struct X { int n; }; int main() { const X x = {10}; int a[x.n] = {1}; }
はx.nがコンパイル時定数でないためill-formedだった(当然constexpr宣言すれば問題はない).しかし,同様のコードをgcc 4.9.0-で実行するとconstであってもコンパイルは通っていた.
この辺り仕様変更があったのだろうが,詳細はネットの海にあるだろう.
ところで,C++11のconstexprには大幅な制限があった.詳細はボレロ村上氏の資料が詳しいので参照されたい.
ラムダ式
ラムダについての詳細な議論は計算機科学の分野なのでここでは一部言及するに止めよう.
ラムダ式はいわゆる無名関数というやつだ.ラムダ計算において,
f(x) = y // 有名(f)関数 λx.y // 無名関数
となる.
C++においてラムダ式は関数オブジェクトであり,それをローカルに定義することができる.
与えられたint型の引数の2乗を返すプログラムをラムダ式を用いたものとそうでないもので記述してみよう.
constexpr int square(int n) { return n * n; } int n = 3; std::cout << square(n) << std::endl; // => 9
auto square = [](int n){ return n * n; }; std::cout << square(3);
短く記述できる関数オブジェクトであり,また,使用する場所と定義する場所が近くなるのでコードの可読性も上昇する.ラムダ式の記述は極めて容易で
[] // キャプチャ () // 引数 {} // ステートメント () // 関数呼び出し式(定義だけなら不要) ;
となる.また,引数は省略可能である.そのため例えばラムダ式を用いたHello, World!は,
[]{ std::cout << "Hello, World!" << std::endl; }();
と記述できる.
初期化子リスト
ユーザー定義クラスで一度に初期化することができる.例えば,
std::vector<int> v = {1, 2, 3, 4};
だ.
右辺値参照
右辺値参照はサイズの大きなオブジェクトのコピーに役立つ.右辺値とは無名の一時オブジェクトだ.
右辺値参照は&&
を用いて参照する.
// C++03 type a; type& aRef = a; // C++11 type a; type&& aRef = a;
enum class
enum classは強い型付けとスコープを持ったenumだ.
暗黙の型変換や名前の衝突を避ける事に役立つ.
enum class name : type { enumrator1, enumrator2 = value, enumrator3, ... , }
C++14
ここで,その機能の一部を提示しよう.
戻り値の型推論
return文から戻り値の型を推論するようになった.但し,複数のreturn文がある場合,型が一致している必要がある.
auto f(auto n) { return n * n; } std::cout << f(3) << " " << f(2.2); // => 9 4.84
ジェネリックラムダ式
C++14において,ラムダ式の引数の型を明確にする必要がなくなった.
auto add = [](auto x, auto y) { return x + y; }; std::cout << add(3, 2.2); // => 5.2
コンパイル時定数の制限緩和
などの制限があったがこれらが撤廃された.但し,Clangしか対応していないのが現状である.
C++14におけるconstexprはより柔軟な表現を手に入れた.
C++14についてもC++11のもの同様ボレロ村上氏の資料が詳しいので参照されたい.
C++14 時代の constexpr プログラミング作法 - ボレロ村上 - ENiyGmaA Code
2進数リテラル
2進数だ.但し浮動小数点数は使えないので注意が必要である.
int x = 0b100; // 4 int y = 0B0011; // 3
終わりに
C++はC++11で大幅な進化を遂げ,C++14ではそれを更に強化する形となった.
C++11,C++14ではC++03では煩雑にならざるを得なかったコードも簡潔に書くことができることが多くなった.
C++11,C++14の機能を用いないレガシーなコードは可読性も低く,実装の手間も大きい.積極的にC++11,C++14の機能を用いるべきである.
明日……と言いたいが,日付を超えてしまったので,今日の分をクック君にお願いします.