C++編【標準ライブラリ】 第15章 ユーティリティ

この章の概要

この章の概要です。

ユーティリティ

この章では、標準ライブラリに含まれている、比較的小粒な便利機能(ユーティリティ)をまとめて紹介します。 互いに関係性の無い機能は、使用するために必要な標準ヘッダもそれぞれ異なっています。

type_info

std::type_info は、型に関する情報をまとめたクラスです。 type_info は、typeinfo という標準ヘッダで定義されており、std名前空間に属しています。

このクラスは、直接インスタンス化するのではなく、typeid演算子を使って、const参照を得て使用します。 typeid演算子についての詳細は、【言語解説】第31章で解説しているので、そちらをご覧下さい。 本章では、簡単に type_infoクラスの機能を紹介します。

nameメンバ関数

nameメンバ関数は、型の名前を表現する文字列を返します。

const char* name() const;

返される文字列が具体的にどのように表現されているかは、コンパイラの実装依存です。 そのため、あるコンパイラで確認した結果が、他のコンパイラでの結果と一致する保証は無いので、 表現に依存するプログラムを書いてはいけません。

beforeメンバ関数

beforeメンバ関数は、2つの type_info を比べて、順位を決定します。

bool before(const type_info& rhs);

*this の方が、rhs より先に来るのなら true を、後に来るのなら false を返します。 この関数における順位とは、実装依存であり、内部的な取り決めに過ぎません。 仮に、2つの type_info が表現している型が、継承関係にあったとしても、基底クラス側が先であるだとかというルールは存在しません

使う機会は少ないかも知れませんが、この関数があることで、複数の type_info をソートすることができます。 これは、map(第9章)のようなコンテナを使った管理に利用できます。

==演算子、!=演算子

2つの type_info を比べて、同じ型の情報であるかどうかを ==演算子で知ることができます。 !=演算子ならその逆で、一致していないことを調べます。

コピー

type_info のコピーは禁止されています。

C++11 (hash_codeメンバ関数)

C++11 では、hash_codeメンバ関数が追加されています。

size_t hash_code() const;

この関数は、保持している情報に応じてユニークになるハッシュ値を返します。 この関数があることで、ハッシュテーブルを使って実装されている unordered_map などの STLコンテナのキーとして、 type_info を使用できるようになります。

C++11 (type_index)

C++11

C++11 で追加された std::type_index は、 std::type_info のポインタをラップしたクラスです。 使用するには、typeindex という標準ヘッダをインクルードする必要があります。

type_info は、typeid演算子を通じて、const参照として受け取るしか無いですが、 参照のままで扱うと、未初期化な状態が許されないため、取り扱いが難しくなることがあります。 また、type_info はコピーができないことも、扱いを難しくします。
type_index は、type_info のポインタをラップしているので、ポインタをコピーするという形で、コピー動作も可能になります。 また、参照と違い、ヌルポインタという状態が持てます。 このように、ラップすることで、type_info の扱いの不便さを解消したものが type_index です。

例として、複数の type_info と、それとペアになる付加情報(ここでは、型ごとにユニークな連番値)を map で管理するとします。 type_index を使うと、次のように書けます。

#include <iostream>
#include <map>
#include <typeinfo>
#include <typeindex>

typedef std::map<std::type_index, int> TypeInfoMap;

int main()
{
	const TypeInfoMap tInfoMap = {
		{ typeid(int), 0 },
		{ typeid(double), 1 },
		{ typeid(char), 2 }
	};

	std::cout << tInfoMap.at(typeid(char)) << std::endl;
}

実行結果

2

限界値

C言語には、 INT_MAX(⇒リファレンス)や INT_MIN(⇒リファレンス)のように、 型ごとに表現できる最大値や最小値を表したマクロが用意されています(C言語編第20章)。 これらのマクロは C++ でも使用できますが、やはり出来ればマクロは避けたいところです。
そこで代わりに、std::numeric_limits というクラステンプレート(【言語解説】第20章)を使用します。 使用するには、limits という標準ヘッダのインクルードが必要です。

numeric_limits は、次のように定義されています。

namespace std {
	template <typename T> class numeric_limits;

	template <> class numeric_limits<int>;
	template <> class numeric_limits<short>;
	template <> class numeric_limits<unsigned int>;
	template <> class numeric_limits<unsigned short>;
	template <> class numeric_limits<float>;
	template <> class numeric_limits<bool>;

	// その他多数
}

このように、クラステンプレートの特殊化(【言語解説】第23章)を活用して実装されています。 上では省略しましたが、C++ で定義されている標準の算術型はすべて存在しています。
このような仕組みで実装されているので、ユーザが自分で作った型に適合する numeric_limits を追加することも可能です。

肝心のメンバですが、これも大量に存在していますが、ここでは最小値と最大値に絞って紹介します。 なお、すべてのメンバが静的になっているので、インスタンス化する必要はありません
最小値は minメンバ関数を、最大値は maxメンバ関数を使用します。

#include <iostream>
#include <limits>

int main()
{
	std::cout << std::numeric_limits<int>::min() << "\n"
	          << std::numeric_limits<int>::max() << std::endl;
}

実行結果

-2147483648
2147483647

C++11 (constexpr)

C++11 では、numeric_limits のすべてのメンバ関数が constexpr(【言語解説】第18章) になっています。

swap

std::swap関数は、2つの値を交換します。 swap関数を使うには、algorithm という標準ヘッダをインクルードする必要があります。

C++11 (ヘッダの変更)

C++11 からは、swap関数は utility という標準ヘッダで定義されるようになりました。

使用例を挙げます。

#include <iostream>
#include <algorithm>

int main()
{
	int a = 10;
	int b = 5;

	std::swap(a, b);
	std::cout << a << ", " << b << std::endl;
}

実行結果

5, 10

swap関数は、関数テンプレートになっているので、型を問わずに使用できます。 更に、標準ライブラリに存在する幾つかの型に対しては、特殊化(【言語解説】第23章)された関数が用意されており、 より効率的な交換処理が行われるようになっています。 例えば、std::swap関数のテンプレート引数が、vector等の STLコンテナ の場合、 メンバ関数版の swap(第4章)を呼び出すコードが実行されます。
なお、2つの引数はともにテンプレートパラメータT の参照なので、異なる型にはできません。

交換処理の実装の都合上、交換する要素が、コピーコンストラクタと代入演算子を使用できなければなりません

C++11 (要件の変更)

C++11 では、std::swap関数で交換する要素が、 ムーブコンストラクタとムーブ代入演算子を使用できなければならなくなりました。 これは、交換処理の実装に、コピー処理ではなく、ムーブ処理を使うようになったためです。 制約は厳しくなりましたが、効率は非常に良くなっています。

C++11 (配列版の swap)

C++11 になって、swap関数に配列を扱えるオーバーロードが追加されました。

#include <iostream>
#include <utility>

namespace {

	template <typename T, std::size_t N>
	void print_array(const T(&array)[N])
	{
		for (std::size_t i = 0; i < N; ++i) {
			if (i > 0) {
				std::cout << ", ";
			}
			std::cout << array[i];
		}
		std::cout << std::endl;
	}

}

int main()
{
	int a[] = { 0, 1, 2 };
	int b[] = { 5, 5, 5 };

	std::swap(a, b);

	print_array(a);
	print_array(b);
}

実行結果

5, 5, 5
0, 1, 2

VisualC++ 2013/2015/2017、clang 3.7 はいずれも使用できます。

swap_ranges

std::swap_ranges関数は、ある2つの範囲内にある要素同士をまとめて交換します。 swap_range関数を使用するには、algorithm という標準ヘッダをインクルードする必要があります。

C++11 で、標準ヘッダが algorithm から utility に変更された std::swap関数と違い、 swap_ranges は、C++11以降でも algorithm のままです。

#include <iostream>
#include <algorithm>

namespace {

	template <typename T, std::size_t N>
	void print_array(const T(&array)[N])
	{
		for (std::size_t i = 0; i < N; ++i) {
			if (i > 0) {
				std::cout << ", ";
			}
			std::cout << array[i];
		}
		std::cout << std::endl;
	}

}

int main()
{
	int a[] = { 0, 1, 2, 3, 4 };
	int b[] = { 5, 5, 5, 5, 5 };

	std::swap_ranges(a, a + 3, b);

	print_array(a);
	print_array(b);
}

実行結果

5, 5, 5, 3, 4
0, 1, 2, 5, 5

第1引数に範囲の始点を、第2引数に終点(の1つ後ろ)を指定します。 第3引数には、もう1つの範囲の始点を指定します。 第1、2引数から要素数が判断できるので、2つ目の範囲の終点は指定しません。 なお、引数はすべて、前方イテレータ(第14章)です。

iter_swap

std::iter_swap関数は、2つのイテレータが指す要素同士を交換します。 この関数については、第14章で解説しているので、そちらを参照して下さい。

C++11 (addressof)

C++11

C++11 で追加された std::addressof関数は、実引数で指定した変数のアドレスを返します。 addressof関数を使用するには、memory という標準ヘッダをインクルードする必要があります。

#include <iostream>
#include <memory>

class MyClass {
public:
	struct Dummy {};

	inline Dummy operator&() const
	{
		return Dummy();
	}
};

int main()
{
	int a = 0;
	std::cout << std::addressof(a) << std::endl;
	std::cout << &a << std::endl;

	MyClass c;
	std::cout << std::addressof(c) << std::endl;
//	std::cout << &c << std::endl;
}

実行結果

009EF980
009EF980
009EF977

アドレスを得るには、普通は &演算子を使いますが、 この演算子はオーバーロードして動作を書き換えることができます(【言語解説】第19章)。 そのため、絶対にアドレスを得られるという保証がある機能が欲しいことがあり、addressof関数が追加されました。
このサンプルプログラムでは、MyClass の &演算子がオーバーロードされており、アドレスを得られないようになっています。 そのため、コメントアウトされている &演算子を使ったコードを有効にすると、コンパイルエラーになります。

addressof関数は、VisualC++ 2013/2015/2017、clang 3.7 のいずれでも使用できます


練習問題

問題@ コピー操作(C++11 ではムーブ操作)の可否によって、std::swap関数の使用の可否が変化することを確認して下さい。

問題A ある型の最小値と最大値のペアを作って返す関数テンプレートを作成して下さい。


解答ページはこちら

参考リンク

更新履歴

'2017/3/25 VisualC++ 2017 に対応。

'2016/10/15 clang の対応バージョンを 3.7 に更新。

'2016/5/8 新規作成。



前の章へ

次の章へ

C++編のトップページへ

Programming Place Plus のトップページへ