初心者C++er Advent Calendar 4日目の記事です。
前日の記事は hotwatermorning さんの コンテナ内で最後に現れる要素を見つける です。
はじめまして。norisio といいます。
C++ のコードを読んでいると、「そんな標準ヘッダがあったのか…」「どういう機能のライブラリなの?」という場面が多くあります。
また、標準ライブラリのような「道具箱」は、知らなければ、問題の解決に組み込むこと_を思いつくこと_もできません。
そこで、この記事では、標準ライブラリ(ヘッダ)をゆるく分類しながら、 __3行以内__の説明とコード片で紹介をしてみます1。
趣旨としては、「使える」ことではなく、__「聞いたことがあったな〜」という状態__を目指します。
さらにこの記事をきっかけに、データ構造やマルチスレッドに興味をもっていただけたらこれ以上の幸いはありません。
入出力系
<iostream>
, <ios>
, <istream>
, <ostream>
<iostream>
は「標準出力、標準入力」(かんたんには「ターミナルへの入出力」)を扱うオブジェクト cout
, cerr
, clog
, cin
を提供する。
<ios>
, <istream>
, <ostream>
は、そのようなストリームを一挙に扱うための基底クラスを提供する。
void f(std::ostream& ost, std::string const& message){
ost << message << std::endl;
}
int main() {
f(std::cout, "標準出力へ");
f(std::cerr, "標準エラー出力へ");
}
<iomanip>
入出力ストリームの 変換方法 を指示するための道具 「マニピュレータ」 を提供する。
数値の出力幅、0埋め、浮動小数の桁数を指定することなどができる。
C 言語の printf
の第1引数に指定するフォーマットに近い機能がある。
std::cout
<< 3.14 << std::endl
<< std::setprecision(1) // 浮動小数点数を出力する精度を1桁にする
<< 3.14 << std::endl;
// => 3.14
// 3
<sstream>
, <fstream>
<sstream>
は、文字列 string を対象とした入出力ストリーム、
<fstream>
は、ファイルを対象とした入出力ストリームを提供する。
istream
/ostream
を継承していて、cin
や cout
などと同じように使うことができる。
// <iostream> の例のものと同じ
void f(std::ostream& ost, std::string const& message){
ost << message << std::endl;
}
int main() {
std::ofstream file("hoge.txt", std::ios::out);
f(file, "ファイルへ"); // hoge.txt に「ファイルへ」が書き込まれる
}
データ構造・コンテナ系
<iterator>
下で紹介する「コンテナ」を統一的に扱うための、 イテレータ に関する機能を提供する。
「インタフェースを統一することで、同じアルゴリズムを様々なコンテナに適用できる」という思想のもとにある。
参考: イテレータの解説をするなんて今更佳代 (初心者C++er AdC 2016)
コンテナ
コンテナ は、キューやスタックのようなデータ構造を一般的な型 T のデータに対して提供するクラス群をいう2。
それぞれのデータ構造で使用可能なイテレータを備えている。
構造によって得意な操作が異なり、「どのように追加・削除したいか」や、「検索の計算量」によって使い分けるとよい。
<array>
, <vector>
<array>
は 固定長配列 (コンパイル時にsizeが決定)、
<vector>
は 可変長配列 (実行時にいつでもsize変更可能)を提供する。
可変長配列は、末尾の要素のみの追加・削除を得意とする。
std::array<int, 3> arr; // array にはテンプレート引数としてサイズを渡す
arr[0] = 3; // 通常の配列と同じアクセスができる. 範囲チェックは無い
arr.at(1) = 1; // at を使うと添字の範囲チェックがされる
// arr.at(3) = 4; // 添字範囲外、例外が送られる
std::vector<int> vec1; // vector はサイズが決まらなくても宣言できる
std::vector<float> vec2(3); // サイズを指定した宣言
std::vector<double> vec3(3, 3.14); // サイズと初期値(すべての要素)を指定した初期化
vec1.resize(100); // サイズの変更が可能
vec1.size() // => 100
<queue>
, <deque>
<queue>
は キュー の構造、
<deque>
は 両端キュー の構造を提供する(__d__ouble-__e__nded __que__ue)。
両端キューは、先頭・末尾の要素の追加・削除を得意とする。
std::deque<std::string> dq;
dq.emplace_back ("丙"); // 末尾に構築
dq.emplace_back ("丁");
dq.emplace_front("乙"); // 先頭に構築
dq.emplace_front("甲");
for(auto const& element: dq){
std::cout << element << std::flush;
}
// => 甲乙丙丁
<list>
, <forward_list>
<list>
は 双方向リスト の構造、
<forward_list>
は、 単方向リスト の構造を提供する。
リストは、任意の位置の要素の追加・削除を得意とする。
std::list<std::string> list;
list.emplace_back("首"); // 末尾に構築
list.emplace_back("都");
list.emplace_back("東");
list.emplace_back("京");
list.emplace_back("都");
auto itr = list.begin(); // 最初("首")を指すイテレータを取得
++itr; // 2つ前に進める
++itr;
list.erase(itr); // itr の指す先(list の3つ目の要素 "東")を削除
for(auto const& element: list){
std::cout << element << std::flush;
}
// => 首都京都
<map>
, <unordered_map>
連想配列 を提供する。
連想配列は、他のプログラミング言語では 辞書 dictionary, ハッシュマップ hash map などと呼ばれる ものと近い。
std::unordered_map<std::string, float> a_human; // キーの型 string, 値の型 float
a_human["身長"] = 129.3f; // string をキーとしてアクセス. キーが存在しない場合は作られる
a_human["体重"] = 31.4f;
std::cout << a_human["身長"] << std::endl; // 読み取りも同様
// => 129.3
<set>
, <unordered_set>
集合 を提供する。
重複要素のない set
, unordered_set
、
重複要素もありの multiset
, unordered_multiset
がある。
std::set<std::string> from_prefs;
from_prefs.emplace("群馬県");
from_prefs.emplace("長野県");
from_prefs.emplace("山梨県");
from_prefs.emplace("群馬県"); // すでに存在する要素を重ねて構築する
for(auto const& element: from_prefs){
std::cout << element << std::endl;
}
// => 山梨県
// 長野県
// 群馬県 : 2回追加したが、要素は1つしかない
アルゴリズム
<algorithm>
上に挙げたコンテナ達は、イテレータのインタフェースを備えている。
それを利用して、要素の 検索, ソート, 条件判定 などの操作を統一的に行える関数を提供する。
std::vector<int> nums { 2, 7, 1, 8, 2, 8, 1, 8, 2};
// 偶数の個数を数える
auto num_of_even
= std::count_if(std::begin(nums), std::end(nums), [](int x){return x % 2 == 0;});
std::cout << num_of_even << std::endl;
// => 6
// 奇数のみ取り出してくる
decltype(nums) odds;
std::copy_if(std::begin(nums), std::end(nums), std::back_inserter(odds),
[](int x){return x % 2 != 0;});
for(auto element: odds){
std::cout << element << ", ";
}
// => 7, 1, 1,
<numeric>
イテレータを利用し、整数列(連番)を生成する関数、
連続する要素の__和(総和、部分和)、隣接要素の__差、
2つの連続する要素の__内積__ といった数値的な処理を提供する。
std::vector<int> seq(10);
// seq を連番で初期化する
std::iota(std::begin(seq), std::end(seq), 1); // 第3引数 は 連番の初期値
// seq => 1, 2, ..., 10
// seq の総和を取る
auto sum
= std::accumulate(std::begin(seq), std::end(seq), 0); // 第3引数 は 加法の零元を渡す
std::cout << sum << std::endl;
// => 55
コンテナでない、データ格納系
<valarray>
数値計算に特化した 配列 valarray
を提供する。
vector
に似ているが、 valarray
どうしの演算子が定義されており、要素指向計算が書きやすい。
コンパイラによるSIMD 化の恩恵も受けやすい。
<bitset>
ビット集合 を提供する。
bitset
どうしの論理演算や、特定ビットへのアクセスを行うことができる。
std::bitset<8> octet[4]; // 8bit の bitset を4つ持つ配列
octet[3].set(7); // 下位ビットから7番目 (つまり最上位ビット) を 1 にする
octet[3].set(6);
octet[2].set(7);
octet[2].set(5);
octet[2].set(3);
octet[0].flip(); // ビットを反転する
for(int i=3; i>=0; --i){
std::cout << octet[i] << " ";
}
// => 11000000 10101000 00000000 11111111
std::cout << std::endl;
for(int i=3; i>=0; --i){
std::cout << octet[i].to_ulong() << ".";
}
<tuple>
タプル を提供する。
タプルは、型の異なる複数の要素をまとめて扱うことができる。
格納する型の種類・順番はテンプレート引数であり、コンパイル時に決定している必要がある。
std::tuple<std::string, int> make_student(){
// std::make_tuple 関数で簡単にタプルの値を生成できる
return std::make_tuple(std::string("スネ夫"), 11);
}
int main(){
std::string name;
int age;
// tie は、変数への参照からなる tuple を生成する
// そこに tuple を代入することで多値代入を再現できる
std::tie(name, age) = make_student();
std::cout << name << "(" << age << ")" << std::endl;
}
<functional>
関数オブジェクト function
を提供する。
function
には、()
で呼び出すことのできるものを全て格納できる。
そのほかにも、__引数の部分適用__や、__四則演算__などの関数オブジェクト自体も提供する。
void hello(){
std::cout << "Hello" << std::endl;
}
int main() {
std::function<void()> f1, f2; // void(): 返り値がvoid, 引数なしの関数
std::function<int(int, int)> f3; // int(int, int): 返り値がint, 引数がint 2つの関数
f1 = hello; // フリー関数 hello の関数ポインタ
f2 = [](){std::cout << "Good evening" << std::endl;}; // ラムダ式(関数オブジェクト)
f3 = std::plus<int>(); // <functional> で提供される「int同士を加算する」関数オブジェクト
f1(); // => Hello
f2(); // => Good evening
std::cout << f3(1000, 500);
// => 1500
}
ユーティリティ系
<memory>
, <scoped_allocator>(省略)
<memory>
は、__スマートポインタ__などのメモリ確保・解放関連機能を提供する。
スマートポインタは、動的に確保したメモリを管理し、自動で解放するクラスである。
struct SomeClass{
int member;
void printMember(){
std::cout << member << std::endl;
}
SomeClass(){
std::cout << "Constructed" << std::endl;
}
~SomeClass(){
std::cout << "Destructed" << std::endl;
}
};
int main() {
// new した SomeClass へのポインタの管理を unique_ptr に任せる
std::unique_ptr<SomeClass> ptr_to_instance( new SomeClass );
// 通常のポインタと同様に "->" でメンバにアクセスできる
ptr_to_instance -> member = 42;
ptr_to_instance -> printMember();
// スコープが終わり変数の寿命が尽きると、自動でインスタンスが破棄される
}
// => Constrcuted
// 42
// Destructed
<chrono>
時計・計時関連 の機能を提供する。
システムの時刻を取得する、プログラム内で経過時間を計測することなどが可能。
時間単位(秒、分など)のクラスも提供され、キャストにより換算ができる。
using std::chrono::system_clock;
using std::chrono::duration_cast;
using std::chrono::seconds;
// 処理の開始時刻と終了時刻を記録することで、処理時間を測定する
system_clock::time_point begin = system_clock::now();
very_long_processing();
system_clock::time_point end = system_clock::now();
// time_point(時刻) - time_point(時刻) の演算は duration(幅を持つ時間) を返す
auto duration_of_process( end - begin );
// seconds(秒) 単位に変換し、数値として表示
std::cout << duration_cast<seconds>(duration_of_process).count() << std::endl;
<regex>
正規表現 を提供する。
<locale>
国ごとに異なる表現 (日付・時刻・通貨) を扱うためのクラス・関数を提供する。
数値系
<complex>
複素数 を提供する。
std::complex<double> z(20.0, 30.0);
z.real(); // 実部 20.0
z.imag(); // 虚部 30.0
std::abs(z); // 絶対値 36.06
std::arg(z); // 偏角(rad) 0.983
std::conj(z); // 複素共軛 (20.0, -30.0)
decltype(z) z2(0.5, 1.2);
z + z2; // 加算 (20.5, 31.2)
<random>
乱数 を提供する。
/*
* ハードウェア乱数は周期をもたないが, 低速である.
* 擬似乱数は周期をもつが, 高速である.
* 一般的な使い方では, 十分に周期の長い擬似乱数生成器(mt19937など)を,
* ハードウェア乱数を使って初期化する. 以降は擬似乱数生成器の返す結果を用いる.
*/
std::random_device seed_generator; // 擬似乱数シードのためのハードウェア乱数
std::mt19937 rnd_engine(seed_generator()); // メルセンヌツイスタ擬似乱数をシードで初期化
double constexpr dist_mean = 50.0;
double constexpr dist_stddev = 10.0;
std::normal_distribution<> dist(dist_mean, dist_stddev); // 正規分布: 平均と標準偏差を与える
dist(rnd_engine); // => 分布 dist にしたがった乱数が生成される
"コンパイル時処理" 系
<type_traits>
型特性を提供する。
型特性とは、const/volatile
修飾の有無や、特定の演算の可否などの、型に関する情報。
テンプレートで受け取った型ごとに処理を変えるときなどに利用される。
// 「型が符号付き算術型か」
static_assert(std::is_signed<int>::value, ""); // OK
static_assert(std::is_signed<unsigned int>::value, ""); // assertエラー
// 「型が const 修飾されているか」
static_assert(std::is_const<float>::value, ""); // assertエラー
static_assert(std::is_const<float const>::value, ""); // OK
// 型の const 修飾を除去
using new_type = std::remove_const<double const>::type;
// 「型が同一かどうか」
static_assert(std::is_same<new_type, double>::value, ""); // OK
<ratio>
コンパイル時有理数計算 を提供する。
マルチスレッド系
<thread>
スレッド を提供する。
スレッドとは、1つのプロセス内に1つ以上並行で流れる、プログラムのコンテキストをいう。
「あるスレッドが IO を待つ間、他のスレッドが計算をする」等の並行・並列処理を可能にする。
std::thread
th1( [](){ /* 処理1 */ } ),
th2( [](){ /* 処理2 */ } ); // スレッドが2つ起動され、処理1, 処理2が並行で行われる
th1.join();
th2.join(); // 2つのスレッドの終了を待つ
<atomic>
アトミック操作 や、それが可能かどうかのフラグを提供する。
アトミック操作とは、 CPU への命令が分割されない、「最小の」操作であり、
マルチスレッドプログラムで必要になることがある。
<mutex>
, <shared_mutex>
相互排他 を提供する。
相互排他(mutex = mutual exclusion) とは、あるスレッドがリソースを使用する際、
他のスレッドと同時に、そのリソースを操作(データ競合)しないようにする仕組みである。
<condition_variable>
条件変数 を提供する。
条件変数(condition variable) とは、特定の条件が満たされるまでスレッドの実行を止めることで、
データ競合を防ぐ仕組みである。
<future>
非同期実行 のパターンを構築するための、
future
, promise
クラス、 async()
関数などを提供する。
参考: C++で簡単非同期処理(std::thread,std::async)
言語サポート系
<new>
グローバルな operator new
を定義する。
また、メモリ確保に失敗した場合に関する型・関数や、例外についての機能も提供する。
<typeinfo>
, <typeindex>
実行時型情報(RTTI) に関する機能を提供する。
RTTI とは、ポリモーフィックな型の実体に対して、実行時にその実体の型を取得できることをいう。
関連: dynamic_cast
参考: C++のtypeidとtype_infoとtype_index
<exception>
, <stdexcept>
<exception>
は__例外__ に関する機能を提供し、 <stdexcept>
は、一般的な例外クラスを提供する。
<stdexcept>
の例外クラスは、logic_error
と runtime_error
に分類される。
<system_error>
OS が出力するエラーを例外として扱う機能を提供する。
おわりに
boost などのヘッダオンリーライブラリであっても、各開発環境に用意するのは少し手間のかかるものです。
標準ライブラリを活用して、可搬性の高いプログラムを書きたいですね。
明日は、 sumomoneko さんです。