C++0x
出典: フリー百科事典『ウィキペディア(Wikipedia)』
C++0x (シープラスプラス・ゼロエックス)は、プログラミング言語 C++ の次期標準として、2009年発行に向け策定中の改訂案を指す仮の通称である。通称 C++98 あるいは C++03 と呼ばれる現行 C++ 標準(1998年発行、2003年改訂の ISO/IEC 14882:2003)に対し、コア言語への機能追加や標準C++ライブラリの拡張を予定しており、 C++ Technical Report 1 ライブラリの大部分を(おそらくは数学的特殊関数ライブラリを除いて)取り込む方向である。標準化の作業はまだ進行中であり、本項は必ずしも最近の C++0x の状況を反映してはいない。次期 C++ 標準の現在の状況は ISO C++ 委員会ウェブサイト を参照のこと。最新のレポート N2652 は、2008年7月に発行されている。
ISO/IEC JTC1/SC22/WG21 C++ 標準化委員会は、2009年中にこの新標準を出す(そして今の仮称 C++0x が C++09 になる)ことを目指している。そのためには、2008年には ISO 加盟各国の承認を求める準備が出来ていなければならないため、委員会は2006年までに受け入れた提案に集中しそれより新しい提案を無視することを決定した[1]。
C++ のようなプログラミング言語は、言語自身の定義を進化させる過程を経る。このことにより必然的に既存のコードとの互換性の問題が発生するものである。実際に C++ での開発過程でもしばしば発生しているが、ビャーネ・ストロヴストルップ (C++ の開発者であり、標準化委員会のメンバーでもある) の声明によれば、新規格は現行規格とほぼ 100% の互換性を保つとされている[2]。
目次 |
[編集] 変更の候補
C++ への修正はコア言語と標準ライブラリの双方に及ぶ。
新規格の策定に向けて、委員会はいくつかの方向性を示した:
- C++98 との、またおそらく C との安定性と互換性の維持。
- 新機能の追加の際には、コア言語の拡張よりも標準ライブラリの拡張を優先する。
- プログラミング技術を向上させるような変更を優先する。
- 特定のアプリケーションにのみ有効な機能を導入するより、システムやライブラリの設計が容易になるような改良を行う。
- 現在行われている安全でないテクニックに対するより安全な代替を提供し、型安全性を高める。
- 効率を高め、ハードウェアとの直接的な連携も可能にする。
- 現実的問題に対する適切な解決法を用意する。
- "ゼロ・オーバーヘッド原則" (ある機能を使用するための追加サポートは、その機能を使用しない場合は影響を及ぼさない) を実践する。
- エキスパート用の機能を削らずとも学習・指導が簡単になるよう、C++ を簡単にする。
初心者はプログラマ人口の多いほうに行きたがるものであり、多くの初心者は自身の分野で使える知識を得たが最後、さらなる C++ への知識を深めることを考えないものであるので、初心者への配慮は重要である[1]。また、C++ の広大さと使い方 (アプリケーションの各分野やプログラミング・スタイルも含む) を考えると、どれだけ経験を積んだプログラマも新しいプログラミングパラダイムの前では初心者になり得ることからも重要である。
[編集] C++ コア言語への拡張
C++ 評議の中心はコア言語の開発であり、C++0x がお目見えする日はこの部分の進展具合に左右される。
C++ コア言語の特筆すべき改良点には、マルチスレッドのサポートや、ジェネリックプログラミングのサポート、一様な初期化構文やパフォーマンス向上等が挙げられる。
このページでは、コア言語の機能拡張や変更点を、実行時パフォーマンス向上、ビルド時パフォーマンス向上、使いやすさの向上、全く新しい機能、の四つに分けて説明する。機能によっては複数の項目に該当するが、最もよく当てはまる項目で述べることとする。
[編集] コア言語の実行時パフォーマンス向上
以下の機能は、主に何らかのパフォーマンス向上を狙ったものである。スピードの向上とメモリ効率の改善の両方が含まれる。
[編集] 右辺値参照と Move semantics
関数が値を一時変数 (代入演算子の右側に置かれることから、右辺値と呼ばれる) への参照として受け取った時、一般の変数への参照を受け取った場合よりも効率的な実装を行えることが期待できる。しかしながら、現行の C++ 標準では、一時変数は const & 型としてしか関数に渡すことが出来ず、渡された値が、本物の右辺値なのか、それとも一般のオブジェクトなのかを関数側で区別することも出来ない。それに加え const で修飾されているので、渡されたオブジェクトを実際に変更することもできない。
C++0x では、右辺値参照と呼ばれる新たな参照型が追加された。表記は 型名 && となる。これにより、右辺値を変更可能なまま関数に渡すことができ、また、オブジェクトに対するmove semanticsを実現できる。
例えば、std::vector は内部的には C スタイル配列のサイズ付きのラッパである。vector の一時変数が生成され関数から返されるとき、現行では新たな vector を生成してそこに全ての右辺値データをコピーしないといけない。その後、一時変数は破壊され、内容は削除される。
右辺値参照を用いる場合、std::vector への右辺値参照を取る vector の "move constructor" を用いることで、単に右辺値から配列へのポインタを取り出してコピーし、空のオブジェクトを残す、ということが出来る。この場合、配列のコピーは起こらず、空の一時変数を破壊してもメモリの破壊は起こらない。関数が vector の一時変数を返す場合、std::vector<> && を返しさえすればよい。vector に move constructor が無い場合、通常通りにコピーコンストラクタが const std::vector<> & として呼ばれる。 move constructor がある場合、move constructor が呼ばれ、メモリの割り当てが回避できる。
安全上の理由から、右辺値として宣言された名前つきの変数をそのまま右辺値として関数に渡すことはできない。std::move() を明示的に呼び出すことで、この制限を回避できる。
bool is_r_value(int &&) { return true; } bool is_r_value(const int &) { return false; } void test(int && i) { is_r_value(i); // false is_r_value(std::move(i)); // true }
右辺値参照の文言の特性と左辺値参照(通常の参照)の文言の若干の修正により、右辺値参照を使って完全な関数転送を開発者が提供できるようになる。可変長引数テンプレートと組み合わせ、関数テンプレートから、決まった型の引数を取る別の関数へと引数を転送することが出来る。これは、コンストラクタ引数の転送に最も有用であり、引数に基づいて自動的に的確なコンストラクタを呼ぶようなファクトリ関数の生成に使用できる。
[編集] 一般化された定数式
C++ には既に定数式が存在している。定数式とは、3 + 4 のように、常に同じ結果を返し副作用の無いものである。定数式はコンパイラの最適化の対象となり、多くの場合コンパイル時実行が行われ、プログラム中にはその結果が格納される。また、C++ の仕様中でも、多くの箇所で定数式の使用が必要となる。配列の定義にも定数式が必要であるし、列挙子の値にも必要である。
しかし、関数呼び出しやオブジェクトコンストラクタが出現すると、定数式ではなくなる。単純な例を挙げると:
int GetFive() { return 5; } int some_value[GetFive() + 5]; // 10 要素の整数配列を作りたいが、C++ では不正。
GetFive() + 5 が定数式でないため、これは C++ では不正となる。実際には GetFive は実行時に一定値を返すが、コンパイラにそれを知らせる方法がないのである。理論上、関数はグローバル変数に影響を与える、実行時に結果が変わる他の関数を呼ぶ、などの理由がある。
C++0x では、キーワード constexpr が導入される。これにより、関数やオブジェクトコンストラクタがコンパイル時定数であることを保証することが出来る。上の例は、以下のように書き直せる:
constexpr int GetFive() { return 5; } int some_value[GetFive() + 5]; // 10 要素の整数配列を作る。C++0x では正しい。
これによりコンパイラは、GetFive がコンパイル時定数であることを理解し、検証できる。
constexpr を関数に使用する場合、関数が出来ることは非常に制限される。まず、関数は非 void 型を持たねばならず、内容として "return expr" の形を持たねばならない。そして、引数を置き換えた後、expr は定数式でなくてはならない。ここでいう定数式では、constexpr として定義された他の関数を呼ぶか、他の定数式データ変数を使うかしかできない。さらに、定数式内ではあらゆる形の再帰はできず、 constexpr が付けられた関数は定義されるまで翻訳単位中で呼ぶことはできない。
定数式の値として、変数を定義することもできる:
constexpr double forceOfGravity = 9.8; constexpr double moonGravity = forceOfGravity / 6;
定数式データ変数は暗黙的に const である。定数式データ変数には、定数式の結果か定数式コンストラクタの結果のみを格納できる。
ユーザー定義型から定数式データ値を作るには、コンストラクタを constexpr として宣言すればよい。定数式関数と同様、定数式コンストラクタは翻訳単位中で使用する前に定義されてなくてはならない。そして、関数本体は空でなくてはならず、メンバを定数式で初期化しなくてはならない。また、このような型のデストラクタは自動生成のものであるべきである。
constexpr として生成された型のコピーも、constexpr として定義されるべきである。こうすることで、constexpr な関数から返された値もconstexprとなる。コピーコンストラクタや演算子オーバーロードといった、クラスの全てのメンバ関数も、定数式関数の定義と同様 constexpr として宣言できる。これにより、コンパイラはクラスのコピーやその他の処理をコンパイル時に行うことが出来る。
定数式関数・コンストラクタは、非 constexpr パラメータで呼ぶことが出来る。constexpr な整数リテラルが非 constexpr 変数に代入できるように、constexpr 関数を非 constexpr パラメータで呼び出せるし、その結果を非 constexpr 変数に格納できるのである。constexpr キーワードは、式の全ての内容が constexpr である場合のコンパイル時定数性の可能性をコンパイラに伝えるだけのものである。
[編集] Plain Old Data 型の定義の修正
現行の C++ 標準では、構造体が Plain Old Data (POD) 型として扱われるようにするためにはいくつかのルールに従う必要がある。C と互換性のあるオブジェクト内部レイアウトを実装するためには、制約に従わなくてはならない、というのはもっともである。しかし、C++03 におけるルールは非常に厳しく、POD 型にする利益以上に面倒なものになってしまっている。
C++0x では POD の定義に関して、いくつかルールを緩和する予定である。
クラス・構造体が POD であるとは、それが trivial であり、standard-layout であり、POD 型でない非 static メンバを持たないことをいう。trivial なクラス・構造体は、以下のように定義される:
- コンパイラ定義のデフォルトコンストラクタを持つ。コンストラクタの default 指定の記法を用いてよい (
SomeConstructor() = default;)。 - コンパイラ定義のコピーコンストラクタを持つ。default 記法を用いてよい。
- コンパイラ定義の代入演算子を持つ。default 記法を用いてよい。
- コンパイラ定義で、非 virtual のデストラクタを持つ。
Standard-layout のクラス・構造体は、以下のように定義される:
- 全ての非静的メンバが standard-layout 型である。
- 全ての非静的メンバに、同じアクセス制御 (public, private, protected) がかかっている。
- 仮想関数を持たない
- 仮想基底クラスを持たない
- 全ての基底クラスが standard-layout 型である。
- 一つ目に定義された非静的メンバと同じ型の基底クラスを持たない。
- 非静的メンバを持つ基底クラスを持たない。もしくは、導出クラスが非静的メンバを持たず、高々一つの基底クラスしか非静的メンバを持たない。これはつまり、クラス階層において非静的メンバを持ってよいクラスは一つだけである、ということである。
[編集] コア言語のビルド時パフォーマンス向上
[編集] 外部テンプレート
現行の C++ 標準では、ある翻訳単位で完全に引数が特定されたテンプレートが見つかった際、コンパイラは常にそのテンプレートを実体化する。このことはコンパイルの時間を劇的に増加させる。特に、同じパラメータを用いたテンプレートが複数の翻訳単位で実体化されるときは顕著である。そして、C++ にそのようなテンプレートの実体化を止めさせる手段も無いのである。
C++0x では、外部テンプレートの考え方を導入する予定である。C++ には既に、特定の場所で強制的に実体化させるための構文がある:
template class std::vector<MyClass>;
C++ に足りないのは、ある翻訳単位ではテンプレートの実体化をしないことをコンパイラに宣言する方法である。C++0x では、以下のように構文が拡張される:
extern template class std::vector<MyClass>;
これにより、コンパイラはこの翻訳単位ではテンプレートの実体化をしないようになる。
[編集] コア言語の使いやすさの向上
以下の機能は、主に言語を使いやすくするためのものである。これには、型安全性や、コード表現の削減、間違ったコードが書かれにくくする、などが含まれる。
[編集] 初期化リスト
現行の C++ 標準では、初期化リストの考え方を C から拝借している。これはつまり、構造体のメンバ定義の順の引数リストを用いて、構造体や配列を生成できるようにするものである。初期化リストは再帰的に適用されるので、構造体の配列や構造体を含む構造体にも用いることができる。静的なデータの初期化や構造体を特定の値に初期化したいときなど、初期化リストは有用なものである。一方、C++ にはコンストラクタがあるため、オブジェクトの初期化処理を複数持つことができる。しかし現在のコンストラクタだけでは、先ほど述べた初期化リストの完全な置き換えにはなっていない。今の C++ では Plain Old Data (POD) 型と確認された構造体・クラスにしか前述の機能は適用できない。非 POD クラスには適用できず、std::vector や boost::array といった C++ スタイルの有用なリスト型にも使えないのである。
C++0x では、初期化リストの考え方が型へと結び付けられる。これには、std::initializer_list を用いる。コンストラクタ等の関数は、初期化リストを引数として取ることができる。例を挙げる:
class SequenceClass { public: SequenceClass(std::initializer_list<int> list); };
これにより、SequenceClass は、整数のシーケンスから構築されるようになる。例えば:
SequenceClass someVar = { 1, 4, 5, 6 };
このコンストラクタは、初期化リストコンストラクタと呼ばれる特殊なコンストラクタである。このコンストラクタを持つクラスは、一様な初期化構文の適用の際に特別に扱われる。
std::initializer_list<> クラスは、C++0x 標準ライブラリのファーストクラスの型である。しかし、これを構築できるのは、C++0x コンパイラが { } 構文を用いて静的に構築する場合だけである。構築された後でコピーすることも出来るが、参照によるコピーだけである。初期化リストは定数であり、一度生成されたらそのメンバを変更することは出来ず、メンバ中のデータも変更できない。
initializer_list は実際の型であるため、クラスコンストラクタのみならず別の場所で使うこともできる。一般の関数も引数として初期化リストを取れる。例えば:
void FunctionName(std::initializer_list<float> list); FunctionName({ 1.0f, -3.45f, -0.4f });
[編集] 一様な初期化構文
標準 C++ には型を初期化するときの問題点がある。初期化に複数の方法があり、しかもお互いを取り替えたときに同じ結果になるわけではないのである。例えば、伝統的なコンストラクタの構文は、コンパイラが間違えないようにするため、関数宣言のように書かないといけない。その一方、C 互換の構造体には C 形式の初期化リストが適用できるのである。
C++0x には、どんなオブジェクトにでも働く、完全に一様な初期化の構文が導入される。これにより、初期化リストの構文が拡張される:
struct BasicStruct { int x; float y; }; struct AltStruct { AltStruct(int _x, float _y) : x(_x), y(_y) {} private: int x; float y; }; BasicStruct var1 = { 5, 3.2f }; AltStruct var2 = { 2, 4.3f };
var1 の初期化は完全に C 形式の初期化リストと同様である。各 public 変数は、初期化リストの値それぞれによって初期化される。暗黙の型変換は必要に応じて使われ、型変換が使用不能ならば、コンパイルに失敗する。
var2 の初期化は、単純にコンストラクタを呼ぶだけである。
一様な初期化による構築では、特定の型を明示することは必ずしも必要ではない:
struct IdString { std::string name; int identifier; }; IdString var3 = { "SomeName", 4 };
この構文は、自動的に const char * のパラメータで std::string を初期化する。以下のようにすることも出来る:
IdString GetString() { return { "SomeName", 4 }; //型を明示していない }
一様な初期化は、コンストラクタの構文を置き換えるものではない。コンストラクタ構文が必要な場面はまだまだあるだろう。クラスが初期化リストコンストラクタ(TypeName(initializer_list<SomeType>);) を持つような場合、初期化リストがシーケンスコンストラクタの型に適合するのならば、他の形式の構築よりも初期化リストコンストラクタが優先して適用される。C++0x バージョンの std::vector は、そのテンプレート型の初期化リストコンストラクタを持つ。これはすなわち:
std::vector<int> theVec = { 4 };
これは初期化リストを取るコンストラクタを呼んでいる。決まったサイズの std::vector を生成するための、サイズを指定する引数ひとつを取るコンストラクタを呼んでいるのではない。そのコンストラクタにアクセスするには、標準のコンストラクタ構文を直接使う必要がある。
[編集] 型推論
現行の標準 C++ (そして C) では、変数の型は使用に際して明示的に指定されねばならない。しかし、テンプレート型やテンプレートメタプログラミングなどにおいては、特に関数の戻り値型が複雑に定義されているような場合、型を簡単に書き下せない。そのような場合には、中間結果を変数に格納することが難しく、メタプログラミングライブラリの内部仕様を知っていなくてはならなくなる場合もある。
C++0x ではこの制約を軽減する方法が二つ提供された。一つ目の方法は、明示的に初期化される変数の定義に auto キーワードを使う方法である。これにより、初期化子によって変数の型が特定される:
auto someStrangeCallableType = boost::bind(&SomeFunction, _2, _1, someObject); auto otherVariable = L"This is a string";
someStrangeCallableType の型は、テンプレート関数 boost::bind の、引数によって定まる戻り値型である。これはコンパイラには容易に分かるが、ユーザーが調べて決めるには難しい。
otherVariable の型はまだ分かりやすいが、多少複雑である。これは const wchar_t * であり、std::string とも同じ型である。
さらに、キーワード decltype によって、式の型をコンパイル時に決定することができる。例を挙げると:
int someInt; decltype(someInt) otherIntegerVariable = 5;
decltype を使うと、変数の型がコンパイラにしか分からなくなるので、auto との組み合わせでの使用が有用である。
auto はコードの冗長性を省くのにも有用である。例えば、以下のように書かれている場合:
for (vector<int>::const_iterator itr = myvec.begin(); itr != myvec.end(); ++itr)
代わりにもっと短く書ける
for (auto itr = myvec.begin(); itr != myvec.end(); ++itr)
コンテナを重ねて使うような場合、この違いは大きい。現在 typedef が果たしているコード量の削減とも組み合わせられる。
[編集] 範囲ベースの for ループ
Boost C++ ライブラリでは、いくつか "範囲" のコンセプトを使用しているものがある。範囲は、リストの二点をもってリストを表現するものであり、コンテナにも似ている。順序付きのコンテナは範囲コンセプトから見れば上位にあり、順序付きコンテナから二つイテレータを持ってくれば範囲を定義できる。これらの考え方や、またこれを元に動作するアルゴリズムが、C++0x の標準ライブラリに組み込まれる予定である。しかし C++0x では、ライブラリだけではなく、範囲コンセプトを使う言語機能ももたらされることになる予定である。
for 文では、範囲の考え方を用いて簡単な反復が行えるようになる:
int my_array[5] = { 1, 2, 3, 4, 5 }; for (int &x : my_array) { x *= 2; }
新形式の for ループでは、一つ目の部分で、範囲を反復するために使う変数を定義する。この変数は、一般の for ループで宣言される変数同様、ループの範囲にのみスコープを持つ。":" に続く二つ目の部分で、反復対象の範囲を指定する。この場合、C スタイルの配列を範囲コンセプトに変換するコンセプトマップがあるので、C 配列を範囲コンセプトとして渡すことが出来る。この機能は、 std::vector などの範囲コンセプトを満たすものなら何にでも適用できる。
[編集] ラムダ関数とラムダ式
C++ 標準では、特に sort や find といった C++ 標準ライブラリのアルゴリズム関数と組み合わせた時に、アルゴリズム関数の呼び出しの近くで述語関数を定義したいと思う機会が多い。しかし、このために言語に用意された仕組みは一つしかなく、関数内部でクラスを定義する方法だけである。この方法は厄介で長ったらしく、コードの流れを妨げがちである。加えて、標準 C++ の規則では、関数の中で定義されたクラスをテンプレートの中で使うことを認めていないので、結局どうしても使うことは出来ない。
明白な解決法は、ラムダ式とラムダ関数の定義を可能にすることである。 C++0x では、ラムダ関数が定義できるようになる。
ラムダ関数は以下のように定義される:
[](int x, int y) { return x + y; }
この匿名関数の戻り値型は decltype(x+y) になる。ラムダ関数が "return 式" の形式である場合だけ戻り値型を省略することができる。そのためラムダ関数が一行に制限される。
もっと複雑な例に対して、以下のように戻り値型を明確に指定することができる:
[](int x, int y) -> int { int z = x + y; return z + x; }
この例では、一時変数 z が途中の値を格納するために作られている。普通の関数と同様、この中間結果の値が呼び出しの間に外部から使用されることはない。
ラムダ関数が値を返さない場合、つまり戻り値型が void の場合は、戻り値型を完全に省略することができる。
さらにラムダ関数と同じスコープで定義されている変数への参照も使うこともできる。この種の変数の一式は、一般にクロージャと呼ばれる。クロージャは以下のように定義して使用する:
std::vector<int> someList; int total = 0; std::for_each(someList.begin(), someList.end(), [&total](int x) { total += x; }); std::cout << total;
これは、リストの全要素の合計を表示する例である。 変数 total がラムダ関数のクロージャの一部として格納される。これは、スタック変数 total への参照なので、 total の値も変更される。
スタック変数に対応するクロージャ変数は、参照シンボルの&を付けずに定義することも出来て、その場合はラムダ関数は値をコピーする。これにより、スタック変数を参照するのかコピーするのかどちらの意図があるのかわかるようになる。スタック変数を参照することは危険であることがある。例えば (C++0x 標準の) std::function オブジェクトに格納するなどして、ラムダ関数を生成したスコープの外側で使いたい場合は、ラムダ関数がスタック変数を参照していないことをよく確認する必要がある。
ラムダ関数がその定義のあるスコープの内側で実行されることが保証されている場合は、スタック変数をひとつひとつ指定せずに、全ての利用可能なスタック変数を使用することができる:
std::vector<int> someList; int total = 0; std::for_each(someList.begin(), someList.end(), [&](int x) { total += x; });
具体的な内部実装は変わり得るが、ラムダ関数にスタック変数への参照が個々に格納されるのではなく、ラムダ関数が生成された場所の関数の実際のスタックポインタが格納されるであろう。
[&] の代わりに[=]を使うと、参照されている変数は全てコピーされ、元の変数の生存期間を越えてもラムダ関数を使うことが出来る。
デフォルトは変数リストと一緒に使うこともできる。例えば、多くの変数を参照で、ひとつの変数だけ値でキャプチャしたい場合は、以下のようにできる:
int total = 0; int value = 5; [&, value](int x) { total += (x * value); };
この例では total は参照として格納され、value はコピーとして格納される。
また、ラムダ関数がクラスのメンバ関数により定義された場合、そのクラスのフレンドであると見なされる。そのようなラムダ関数は、そのクラス型のオブジェクトへの参照を使って、内部のメンバにアクセスできる:
[](SomeType *typePtr) { typePtr->SomePrivateMemberFunction(); };
この例は、このラムダ関数の生成するスコープが SomeType のメンバ関数の中にある場合にのみ動作する。
this ポインタは、各時点でメンバ関数が作動しているオブジェクトを指しているが、その扱いは特別である。ラムダ関数中に明示的な指定が必要になる:
[this]() { this->SomePrivateMemberFunction(); };
[&] または [=] 形式のラムダ関数を用いることにより、自動的に this が使えるようになる。
ラムダ関数はコンパイラ依存の型の関数オブジェクトであり、コンパイラしかこの型の名前を利用できない。ラムダ関数を引数として取得したい場合は、その型をテンプレート型にするか、std::function を作ってラムダの値をキャプチャする必要がある。auto キーワードを使ってラムダ関数をローカル変数に格納できる。
auto myLambdaFunc = [this]() { this->SomePrivateMemberFunction(); };
ただし、ラムダ関数が全てのクロージャ変数を参照でキャプチャしているか、クロージャ変数が無い場合は、生成された関数オブジェクトは std::reference_closure<R(P)> という特別な型が与えられる。R(P) は関数のシグネチャと戻り値型である。この型は std::function でキャプチャするよりも効率的にラムダ関数を表現できるであろう。
std::reference_closure<void()> myLambdaFunc = [this]() { this->SomePrivateMemberFunction(); }; myLambdaFunc();
[編集] 統合された関数構文
標準Cの関数宣言の構文は C 言語の機能を使うには最適であった。C++ が C から離れて進化するにつれて、基本的な構文を維持しつつも、必要ならばその部分を拡張してきた。しかし、C++ がさらに複雑になってくると多数の制約、特にテンプレート関数の宣言に関する制約が露呈した。例えば、次の例は C++03 では認められていない:
template<typename LHS, typename RHS> Ret AddingFunc(const LHS &lhs, const RHS &rhs) { return lhs + rhs; }
Ret 型は、LHS と RHS の型を加算して作られる色々な型である。前述の decltype という C++0x の機能を使っても不可能である:
template<typename LHS, typename RHS> decltype(lhs+rhs) AddingFunc(const LHS &lhs, const RHS &rhs) { return lhs + rhs; }
lhs と rhs がまだ定義されていないため、この例は C++ に適合していない。関数プロトタイプの残りをパーサーが解析し終えた後でしか有効な識別子にならないのである。
この問題に対処するために、C++0x では次のような関数の定義と宣言の構文が導入される:
template<typename LHS, typename RHS> []AddingFunc(const LHS &lhs, const RHS &rhs) -> decltype(lhs+rhs) { return lhs + rhs; }
この構文はよくある関数の宣言と定義にも使うことができる:
struct SomeStruct { []FuncName(int x, int y) -> int; }; []SomeStruct::FuncName(int x, int y) -> int { return x + y; }
[]構文の使い方はラムダ関数と同じである。関数名を取り除くとラムダ式になる (ただしラムダ関数はテンプレートにすることができないので、AddingFunc の例はラムダにすることができない)。関数はブロックスコープに定義することができるので、名前つきのラムダ関数を作ることができる:
[]Func1(int b) { [&] NamedLambda1 (int a) -> int { return a + b; } auto NamedLambda2 = [&](int a) { return a + b; } // この形式の戻り値型の省略はラムダの仕様に適合する。 }
これらの文は等価であり、std::reference_closure<int(int)> 型の変数を生成する。しかし、次の例はそうではない:
[]Func1() { [] NamedLambda1 (int a) -> int { return a + 5; } // ネストされたローカル関数で、C++0x では有効 auto NamedLambda2 = [](int a) { return a + 5; } }
NamedLambda1 変数は通常の C スタイルの関数ポインタであり、あたかも int NamedLambda1(int a) として定義されたかのようになる。NamedLambda2 変数は std::reference_closure<int(int)> 型として定義される。一行目は C++0x におけるネストされた関数の新しい形式を表している。
[編集] コンセプト
C++では、テンプレートクラス・関数は引数とする型に必然的に何らかの制限を課す。例えば、STLコンテナは格納する型にデフォルトコンストラクタを要求する。Foo&型のオブジェクトを受ける関数にはFooのあらゆるサブタイプを渡せる、というようなクラス階層によって提供される動的な多態とは違い、テンプレート引数には、テンプレートが使う演算をサポートするクラスならば何でも渡すことが出来る。しかし、通常の関数の場合は引数に対する要求は明確である(先の例なら、Fooのサブタイプであること)が、テンプレートの場合は、オブジェクトが持たなければならないインターフェースはテンプレート定義の中に隠れてしまっている。コンセプトはテンプレート引数が持たなくてはならないインターフェースを明文化する機構を提供する。
コンセプトを導入する理由の一つは、エラーメッセージの質の改善である。テンプレートが必要とするインターフェースを持っていない型をプログラマが使おうとした場合、コンパイラはエラーを出す。しかし、その種のエラーは理解しがたいものになりやすく、特に初心者には非常に難解である。この理由としては、エラーメッセージにテンプレート引数が省略されずに表示され、非常に長いエラーメッセージが出力されてしまうことが多いことが挙げられる。コンパイラによっては、単純なミスが数キロバイトものエラーメッセージを出す結果になることもある。他の理由として、エラーメッセージがエラーの実際の理由を明確に示していないことがある、ということもある。例えば、コピーコンストラクタを持たないオブジェクトのvectorを構築しようとした場合、大抵の場合のエラーメッセージは「内包するオブジェクトにコピーコンストラクタを呼んでしまったvectorクラス」に言及するものになってしまう。熟練したプログラマでないと、本当のエラーが「vectorに渡した型がvectorの要件を満たしきれていないこと」が解らないだろう。
この問題を解決するため、C++0xでは言語にコンセプトの機能が追加された。コンセプトは、名前付きの構造であり、型が提供しなければならない機能を指定する。この点は、オブジェクト指向プログラミングで、型の行えることの制限の定義を基底クラスにより行うのに似ている。しかしオブジェクト指向プログラミングとは違い、コンセプトの定義自体にはテンプレートに渡される型が明示的に関連付けられず、テンプレート定義の側で結びつけられる:
template<LessThanComparable T> const T& min(const T &x, const T &y) { return x < y ? x : y; }
テンプレート型引数にclassやtypenameを使い任意の型と指定するのではなく、前方で定義されたコンセプトであるLessThanComparableを使用している。テンプレート関数minに渡された方がコンセプトLessThanComparableを満たさない場合、コンパイルエラーとなり、テンプレート実体化に使われた型がLessThanComparableコンセプトに適合しなかったことが報告される。
より一般的なコンセプトの使用の記法は、以下のようになる:
template<typename T> require LessThanComparable<T> const T& min(const T &x, const T &y) { return x < y ? x : y; }
requireキーワードの後には、コンセプト宣言のリストが続く。これにより、複数の型を使うコンセプトを使用できる。また、require !LessThanComparable<T>のように用いることも出来、あるコンセプトに合致する型の使用を禁止することが出来る。これらの機能は、テンプレートの特殊化と同様の方法で使用できる。一般的なテンプレートを少ない機能しか使わないものとして定義し、さらに多機能なコンセプトを用いた特殊化を用意することで、その機能を用いて高いパフォーマンスや高機能性を実現できる。
コンセプトは以下のように定義される:
auto concept LessThanComparable<typename T> { bool operator<(T, T); }
この例に含まれているキーワードautoは、コンセプトに記述された操作を提供するあらゆる型がコンセプトを満たすこととする、ということを示すものである。autoキーワードが無い場合、コンセプトを満たす型を宣言するのにコンセプトマップを使う必要がある。
このコンセプトは、「自身と同じ型の引数を二つ取り、boolを返す演算子 < を持つあらゆる型はLessThanComparableである」ということを示している。演算子は大域関数ではなく、型Tのメンバ関数でなくてはならない。
コンセプトは、複数の型を含むことも出来る。例えば、二つの型をとり、片方がもう片方の型へと変換できる、ということを表すコンセプトは:
auto concept Convertible<typename T, typename U> { operator U(const T&); }
これをテンプレートで使うためには、コンセプトの一般的使用の記法を使う必要がある:
template<typename U, typename T> require Convertible<T, U> U convert(const T& t) { return t; }
コンセプトは複合できる。例えば、Regularというコンセプトが与えられている場合:
concept InputIterator<typename Iter, typename Value> { require Regular<Iter>; Value operator*(const Iter&); Iter& operator++(Iter&); Iter operator++(Iter&, int); }
InputIteratorコンセプトに渡されたテンプレート引数の一つ目は、Regularコンセプトを満たすことが確認される。
継承を行うように、コンセプトも他のコンセプトから派生できる。そして、クラスの継承のように、派生コンセプトも基底コンセプトの要件を満たす。派生コンセプトは、クラスの派生のように定義される:
concept ForwardIterator<typename Iter, typename Value> : InputIterator<Iter, Value> { //ここに他の要件を追記する。 }
コンセプトに型名 (typename)を結び付けることもできる。これにより、型名が使用可能であることをコンセプトの要件として表現できる:
concept InputIterator<typename Iter> { typename value_type; typename reference; typename pointer; typename difference_type; require Regular<Iter>; require Convertible<reference, value_type>; reference operator*(const Iter&); // 参照外し Iter& operator++(Iter&); // 前置インクリメント Iter operator++(Iter&, int); // 後置インクリメント // ... }
コンセプトマップは、コンセプトに型を明示的に結びつけるのに使われる。これにより、型が(可能ならば)型定義を変えることなくコンセプトに適合することを示せる。例を挙げる:
concept_map InputIterator<char*> { typedef char value_type; typedef char& reference; typedef char* pointer; typedef std::ptrdiff_t difference_type; };
このコンセプトマップは、InputIteratorコンセプトを満たす型としてchar*型を与えている。
柔軟性を高めるため、コンセプトマップ自体をテンプレートに出来る。以下の例は、あらゆるポインタ型を扱えるように拡張したものである:
template<typename T> concept_map InputIterator<T*> { typedef T value_type; typedef T& reference; typedef T* pointer; typedef std::ptrdiff_t difference_type; };
さらに、コンセプトマップは、クラスに共通に関連付けられる関数定義などの構造を示す、ミニタイプとしても振る舞うことも出来る:
concept Stack<typename X> { typename value_type; void push(X&, const value_type&); void pop(X&); value_type top(const X&); bool empty(const X&); }; template<typename T> concept_map Stack<std::vector<T> > { typedef T value_type; void push(std::vector<T>& v, const T& x) { v.push_back(x); } void pop(std::vector<T>& v) { v. pop_back(); } T top(const std::vector<T>& v) { return v. back(); } bool empty(const std::vector<T>& v) { return v. empty(); } };
このコンセプトマップは、Stackコンセプトを実装する型を引数とするテンプレートが、std::vectorを引数に取れるように定義している。std::vectorを使えるようにするため、各関数呼び出しをstd::vectorの関数の呼び出しに置き換えている。これを用いれば、究極的には、既に存在するオブジェクトの定義を変えることなく、それをテンプレート関数が使用するインターフェースに適合できる。
最後になるが、静的な表明を用いてコンセプトの各要件を確認できることもできる。実際には静的表明の機能は別の問題に焦点を当てているのであるが、この機能でテンプレートの要する要件を検証することができる。
[編集] オブジェクト構築の改良
現在のC++では、コンストラクタは他のコンストラクタを呼ぶことは出来ず、各コンストラクタでクラスメンバの初期化を全て行わなくてはならない。これはしばしば、初期化コードの重複を招く。また、基底クラスのコンストラクタは、派生クラスに直接は公開されない。つまり、基底クラスのコンストラクタと殆ど同等であったとしても、派生クラス側でコンストラクタを定義する必要がある。他にも、constでないデータメンバは、メンバの宣言時に初期化することが出来ず、コンストラクタで初期化しなければならない。
C++0xでは、このような問題点に対する解決策が提供される。
まず、C++0xでは、コンストラクタが他の同等なコンストラクタを呼び出すこと(委譲)が出来るようになる予定である。これにより、コンストラクタが他のコンストラクタを最小のコード追加で利用できるようになる。他の言語(Java等)では、既にこれを取り入れている物もある。
構文は以下のようになるであろう:
class SomeType { int number; public: SomeType(int newNumber) : number(newNumber) {} SomeType() : SomeType(42) {} };
注意すべき点として、C++03では一つのコンストラクタの動作完了と同時にオブジェクトの構築は完了していると考えることができていたが、C++0xでは一度のオブジェクトの構築に全てのコンストラクタが動作完了しなければならない、と考えなくてはならない点である。複数のコンストラクタが動作することを認めるので、委譲を行う各コンストラクタは完全に構築が完了したオブジェクトに対して動作することを意味する。派生クラスのコンストラクタは、基底クラスでのコンストラクタ間委譲が全て終了した時点で呼び出されることになるであろう。
次に基底クラスのコンストラクタに関して、C++0xでは、基底クラスのコンストラクタを継承する様にクラスに対して指定することが可能になる。これにより、コンパイラは派生クラスのコンストラクタ呼び出しを基底クラスのそれへと転送するコードを生成することになる。注意すべき点は、この機能は、全てのコンストラクタ呼び出しを基底クラスに転送するか、全く転送しないか、の二者択一の機能であることである。他の注意点として、多重継承時の制限もある。同じシグネチャを持つ二つのコンストラクタに転送することは出来ないし、転送先のコンストラクタと同じシグネチャを持つコンストラクタを後で宣言することはできない。
構文は以下のようになるであろう:
class BaseClass { public: BaseClass(int iValue); }; class DerivedClass : public BaseClass { public: using default BaseClass; };
最後にメンバの初期化に関して、C++0xでは、メンバの初期化に以下のような構文を認める予定である:
class SomeClass { private: int iValue = 5; };
この例では、コンストラクタが初期化内容を上書きしない限り、iValueは5に初期化される。上書きする例は次のようになる:
class SomeClass { public: SomeClass() {} explicit SomeClass(int iNewValue) : iValue(iNewValue) {} private: int iValue = 5; };
この場合、空のコンストラクタではiValueはクラス定義に従って初期化されるが、int引数を取るコンストラクタの場合はその引数に従って初期化されることになるであろう。
[編集] ヌルポインタ
現行のC++標準では、定数0に、整定数とヌルポインタという二つの役割が与えられている(この振る舞いは、Cの黎明期(1972年)から続いている)。
長い間プログラマは、0の代わりに定数NULLを使って、この潜在的な曖昧性を大体は回避してきた。しかし、C++に二つ為された設計上の選択が、新たな曖昧性をもたらした。Cでは、NULLはプリプロセッサマクロであり、((void*)0)か0と展開されるよう定義されている。C++では、void*型から他のポインタ型への暗黙の変換は認められないので、Cの一つ目の定義と同じくNULLを定義すると、char* c = NULLのような単純な例でもコンパイルエラーになる。これを修正するため、C++ではNULLは0へと展開される。0は、あらゆるポインタ型への変換が特別に認められているのである。この結果、オーバーロード機構と酷い相互作用を引き起こす。例えば、以下のような宣言があり
void foo(char*); void foo(int);
foo(NULL)として呼び出す場合、foo(int)の方が呼ばれることになる。この挙動は、多くの場合に意図されたものではない。
新標準では、ヌルポインタを指定するためにのみ予約された新たなキーワードが導入されそうである。現時点では、nullptrが提唱されている。nullptrは整数型との比較や代入はできないが、あらゆるポインタ型との比較・代入ができる。
互換性のため、0の現行の機能も残されるが、この新構文が上手くいけば、C++委員会は0とNULLをヌルポインタとして使用することを非推奨と宣言し、意味の重なりを排除するであろう。
[編集] 強い型付けの列挙型
現行のC++標準では、列挙は型安全でない。列挙の型が違うときでさえ、実質的に整数として扱われるのである。これにより、型の違う列挙型の値同士が比較できてしまう。C++03で提供される型安全性は、ある列挙型の変数や整数が暗黙的に列挙型変数に変換されることはない、ということだけである。また、内部的な整数型は明示的に指定されず、実装定義である。さらに、列挙の値のスコープは、それが定義された箇所に同じとなる。そのため、別の場所で定義された列挙に同じメンバ名を持たせることはできない。
C++0xでは、以上のような問題の無い、特別なクラス化された列挙型が導入される。enum class宣言を用いることで、これを表現できる:
enum class Enumeration { Val1, Val2, Val3 = 100, Val4 /* = 101 */, };
この列挙は型安全である。Enum classの値が暗黙的に整数値に変換されることはなく、整数値と比較することもできない(Enumeration::Val4 == 101はコンパイラエラーになる)。
Enum classの内部的な型も明示的に指定できる。デフォルトでは上の例のようにintであるが、以下のように変更できる:
enum class Enum2 : unsigned int {Val1, Val2};
列挙名は列挙のスコープで定義される。列挙名を使うときには、明示的にスコープを指定しなければならない。Enum2::Val1は定義済みだが、Val1は未定義となる。
さらに、C++0xでは普通の列挙にも、明示的なスコープや内部的な型の指定を行うことが出来る:
enum Enum3 : unsigned long {Val1 = 1, Val2};
この場合、列挙名は列挙のスコープで定義される (Enum3::Val1)が、後方互換性のため、列挙名は列挙が定義されたスコープにも定義される。
C++0xでは列挙の前方宣言も可能である。以前は列挙のサイズが内容によって決まっていたため、列挙型を前方宣言することができなかった。アプリケーションで列挙のサイズを決めさえすれば、前方宣言することができる:
enum Enum1; //C++とC++0x両方で不正。サイズが明示されていない。 enum Enum2 : unsigned int; //C++0xでは正しい。 enum class Enum3; //enum class宣言はデフォルトの型が"int"なので、C++0xでは正しい。 enum class Enum4: unsigned int; //C++0xでは正しい。 enum Enum2 : unsigned short; //Enum2が既に違う型で宣言されているので、C++0xでは不正。
[編集] 山括弧
テンプレートを用いた総称プログラミングを導入するのに、新形式の括弧を導入するのが必要であった。そのためC++には、丸括弧"()"、角括弧"[]"、中括弧"{}"に加えて、山括弧"<>"が導入された。しかしこれにより字句的曖昧さが生じ、(プログラマの意図とは違う、という意味で)間違って解析され、構文エラーになるという事態が発生した:
typedef std::vector<std::vector<int> > Table; // OK。 typedef std::vector<std::vector<bool>> Flags; // エラー! ">>"は右シフトと解析される。 void func(List<B>= default_val); // エラー! ">="は比較演算子と解析される。 void func(List<List<B>>= default_val); // エラー! ">>="は右シフト代入と解析される。 template<bool I> class X {}; X<(1>2)> x1; // OK。 X< 1>2 > x1; // エラー! 一つ目の">"は山閉じ括弧と解析される。
C++0xの字句解析では、最も深くネストした開き括弧が山括弧"<"である場合、">"は、次に">"や"="が続いていても山閉じ括弧として扱われる。これにより、上記のエラーは最後のものを除いて修正される。最後のものを修正するには、曖昧さを取り除くために丸括弧を足さなくてはならない。
X<(1>2)> x1 ; // OK。
こうすることで、"("から")"の間については、コンパイラは<>の文字を山括弧と扱わなくなる。
[編集] 明示的な変換関数
C++規格には、一引数コンストラクタを暗黙的な型変換関数として扱われないようにするための修飾子として、explicitキーワードが導入された。しかし、これは変換関数には何の効果も無い。
例えば、スマートポインタのクラスは、本物のポインタと同じように振る舞うためにoperator bool()を持っていることがある。この変換を追加することで、if (smart_ptr_variable)としてそれをテストできる(ポインタが非NULLならtrueを、NULLならfalseと判定できるはずである)。しかし、この変換を認めると、不本意な変換も発生してしまう。C++のboolは算術型として定義されているため、整数型やさらには浮動小数点型としてまで変換されてしまい、ユーザーが意図しない数値型としての動作を引き起こしてしまうのである。
C++0xでは、explicitキーワードを変換関数にも適用できるようになる。コンストラクタへの適用と組み合わせ、暗黙的型変換をさらに防ぐことが出来る。
[編集] テンプレートの別名付け (template typedefs)
現在、テンプレートの別名を使う方法は、全てのパラメータが定義されたテンプレートをtypedefで定義する方法だけである。パラメータが定義されていないものに別名を与えることは出来ない。例えば:
template<typename first, typename second, int third> class SomeType; template<typename second> typedef SomeType<OtherType, second, 5> TypedefName; //現行C++では不正
これをコンパイルすることは出来ない。
C++にはこれを可能にする機能が追加される。正確な構文はまだ出来上がっていないが、以下のようなものになるであろう:
template<typename first, typename second, int third> class SomeType; template<typename second> using TypedefName = SomeType<OtherType, second, 5>;
[編集] 透過的なガベージコレクション
C++0xには、透過的ガベージコレクションの機能は直接には導入されない。代わりに、C++0x標準には、C++でのガベージコレクションの実装を容易にする機能が導入される。
完全なガベージコレクションのサポートは、もっと後の標準やTechnical Reportに回されることになった。
[編集] 制限の無い共用体
C++標準にはunionのメンバになれるオブジェクトの型に対する制限がある。例えば、trivialでないコンストラクタが定義されているオブジェクトは共用体の中に含めることができない。共用体に課された制限の多くは無くてもいいと思われるので、次期標準では参照型を除いて共用体のメンバの型の制限が全廃される。この変更により、共用体は使いやすく強力で有用なものになる。
以下はC++0xで許される共用体の簡単な例である:
struct point { point() {} point(int x, int y): x_(x), y_(y) {} int x_, y_; }; union { int z; double w; point p; // pointがtrivialでないコンストラクタを持つためC++では不正だが、C++0xでは問題ない。 };
この変更は現行の規則を緩めるだけなので、既存のコードを破壊することはない。
[編集] コア言語機能の改良
以下の機能は、従来のC++では全く不可能であったり、異常に冗長な記述が必要だったり、可搬性の無いライブラリを使わないといけなかったような機能を提供するものである。
[編集] 可変長引数テンプレート
現行の標準C++のテンプレート(クラステンプレート・関数テンプレート)は、あらかじめ決められた個数の引数しか取ることができない。C++0xでは、テンプレート定義の際にあらゆる型の引数を任意数取れるようになる予定である:
template<typename... Values> class tuple;
このtupleクラステンプレートは、 テンプレート引数として任意数の型名を取れる:
class tuple<std::vector<int>, std::map<std::string, std::vector<int> > >someInstanceName;
引数の数が0であったとしても、class tuple<> someInstanceNameのように動かすことが出来る。
可変長テンプレート引数の個数に0を認めない場合は、以下のように定義すればよい:
template<typename First, typename... Rest> class tuple;
可変長テンプレート引数を取る関数も定義でき、Cの可変長引数機構に似ているが、型安全な仕組みがもたらされる。
template<typename... Params> void printf(const std::string &strFormat, Params... parameters);
テンプレート定義時にはParamsの左側に...演算子を配置するが、関数シグネチャ中ではParamsの右側に使うことを注意する必要がある。テンプレート引数仕様のように、...演算子を型名の左側におく場合、これは"pack"演算子となる。この演算子は、型が0個以上となり得ることを示す。...演算子が型名の左側にある場合は、"unpack"演算子であり、pack演算子でまとめられた型のそれぞれに対して、複製の処理が行われるようになる。上の例では、printf関数の引数にはParamsにそれぞれの型がまとめられて渡される。
可変長テンプレート引数自体は関数・クラスの実装に使えるものではないため、可変長引数テンプレートの使用は再帰的に行われる。例えば、典型的な例として、C++0xにおけるprintfの代替の定義例を以下に挙げてみよう:
void printf(const char *s) { while (*s) { if (*s == '%' && *(++s) != '%') throw std::runtime_error("invalid format string: missing arguments"); std::cout << *s++; } } template<typename T, typename... Args> void printf(const char* s, T value, Args... args) { while (*s) { if (*s == '%' && *(++s) != '%') { std::cout << value; printf(*s ? ++s : s, args...); // さらなる引数を見つけるため、*s == 0でも呼び出す return; } std::cout << *s++; } throw std::runtime_error("extra arguments provided to printf"); }
これは再帰呼び出しを使っている。可変長テンプレート引数バージョンのprintfは自身を再帰的に呼び出し、argsが空の場合はシンプルな方のprintfが呼ばれることになる。
可変長テンプレート引数に順次アクセスする簡単な方法は無い。しかし、unpack演算子を用いることで、どこでも仮想的にテンプレート引数を解体することができる。
例えば、クラスを以下のように使用できる:
template <typename... BaseClasses> class ClassName : public BaseClasses... { public: ClassName (BaseClasses&&... baseClasses) : BaseClasses(static_cast<BaseClasses&&>(baseClasses))... {} }
unpack演算子により、ClassNameの各基底クラス型が複製され、このクラスは渡された各型の導出クラスとなる。また、ClassNameの基底型を初期化できるように、コンストラクタは各基底クラスへの参照を取る。
関数テンプレートでは、取った可変長引数を先へと転送することが出来る。右辺値参照と組み合わせることで、完璧な転送を行うことが出来る。
template<typename TypeToConstruct> struct SharedPtrAllocator { template<typename ...Args> tr1::shared_ptr<TypeToConstruct> ConstructWithSharedPtr(Args&&... params) { return tr1::shared_ptr<TypeToConstruct>(new TypeToConstruct(static_cast<Args&&>(params)...)); } }
この特殊なファクトリ関数は、メモリリークに対する安全性のため、割り当てられたメモリをtr1::shared_ptrに自動的に包むものである。上記のように、引数リストを解体してTypeToConstructのコンストラクタへと渡している。static_cast<Args&&>(params)構文で、const性などを保ちつつ、引数の適切な型をコンストラクタへと転送することができる。unpack演算子により、それぞれのパラメータに上記の伝播文法を適用できる。
また、テンプレートパラメータの数を以下のように決定できる:
template<typename ...Args> struct SomeStruct { static const int size = sizeof(Args...); }
SomeStruct<Type1, Type2>::sizeは2となり、SomeStruct<>::sizeは0となるであろう。
[編集] 新たな文字列リテラル
現行の標準C++には二種類の文字列リテラルがある。一つ目は、ヌル終端されたconst char型配列を生成する、ダブルクオートで囲まれた形式のものである。もう一つは、ヌル終端されたconst wchar_t型配列を生成する、L で囲まれたものである。しかし、どちらもUnicodeで符号化された文字列リテラルをサポートするものではない。
C++コンパイラでのUnicodeサポートを強化するため、char型の定義が修正され、少なくとも8ビット符号単位のUTF-8符号化形式を格納出来て、コンパイラの基本実行文字セットのあらゆる文字を格納するのに十分な大きさを持つ、とされた。以前は後者だけを満たしていれば良いとされていた。
C++0xでは、UTF-8, UTF-16, UTF-32の三つのUnicode符号化形式がサポートされる。先ほど述べたcharの定義の変更に加え、C++0xには二つの新たな文字型、char16_tとchar32_tが加わる。それぞれ、UTF-16、UTF-32の符号単位を格納するよう設計されている。
以下に、それぞれの符号化形式で文字列リテラルを作る方法を示す:
u8"I'm a UTF-8 string." u"This is a UTF-16 string." U"This is a UTF-32 string."
一つ目の文字列の型は、普通のconst char *である。二つ目は、const char16_t*であり、三つ目はconst char32_t*となる。
Unicode文字列リテラルを作るときには、Unicodeの符号位置を直接に文字列に埋め込めると便利である。C++0xでは、以下のような構文が使えるようになる:
u8"This is a Unicode Character: \u2018." u"This is a bigger Unicode Character: \u2018." U"This is a Unicode Character: \u2018."
'\u'の後の数字は16進数であり、接頭辞'0x'をつける必要は無い。'\u'は16ビットのUnicode符号位置を示すものであり、32ビットのUnicode符号位置を示す場合は'\U'と16進数を用いる。正当なUnicode符号位置のみが入力できる。例えば、UTF-16のサロゲートペア(代用対)のために予約された、0xD800-0xDFFFの範囲の代用符号位置は使用できない。
XMLファイルのリテラルを用いる場合や、スクリプト言語のリテラルを用いる場合など、手動でエスケープしなくてよい文字列も有用である。C++0xでは、raw文字列リテラルが導入される予定である:
R"[The String Data \ Stuff " ]" R"delimiter[The String Data \ Stuff " ]delimiter"
一つ目の場合、[]括弧記号で挟まれた箇所全てが文字列となる。'"'や'\'の文字はエスケープする必要は無い。二つ目の場合、"delimiter[から]delimiter"までが文字列となる。delimiterという文字列は、実際には任意の文字列でよい。これにより、']'文字をraw文字列リテラルで使うことが出来る。
raw文字列リテラルは、ワイドリテラルや各種Unicodeリテラルと組み合わせることができる。
[編集] ユーザー定義リテラル
他の多くの言語と同様、現行の標準C++にも数種のリテラル値がある。例えば、"12.5"はコンパイラがdouble型の浮動小数点値へと変換するリテラル値である。しかし、リテラル値にはいくつもの修飾子がある。"12.5f"というリテラルは、浮動小数点値ではあるがfloat型の値を生成するように伝える。このような修飾子はC++仕様として規定されており、C++のコード中では新たな修飾子を生成することは出来なかった。
C++0xでは、ユーザーが新たな種類のリテラル修飾子を定義できるようにし、リテラル修飾子の文字列を基にオブジェクトを生成できるようになる予定である。
リテラルの変換過程が再定義され、二つの段階、rawとcookedへと分けられた。rawリテラルは特定の型の文字の並びであり、cookedリテラルは単一の型である。例えば、C++リテラルの1234は、rawリテラルとしては'1', '2', '3', '4'という文字の並びであり、cookedリテラルとしては整数値1234である。0xAは、rawリテラルとしては'0', 'x', 'A'であり、cookedリテラルとしては整数値10である。
常にcooked形式として処理される文字列リテラルを除き、リテラルはraw形式にもcooked形式にも拡張される。文字列リテラルが例外なのは、文字列には、文字列の型と特別な意味の指定を行う接頭辞があるからである。
ユーザー定義リテラルは全て接尾辞である。接頭辞リテラルを定義することは出来ない。
raw形式のリテラルを処理するユーザー定義リテラルは、以下のように定義できる:
OutputType operator""_Suffix(const char *literal_string); OutputType someVariable = 1234_Suffix; //operator""_Suffix("1234") と等価
一つ目の文で、接尾辞「_Suffix」を定義している。ユーザーが定義する接尾辞はアンダースコアで始める(アンダースコアで始まらないものは将来の標準のために予約されている)。
二つ目の文では、ユーザー定義リテラル関数によって定義されたコードを実行している。この関数には、Cスタイルの文字列としてヌル終端された"1234"が渡される。
rawリテラルを処理するもう一つの方法は、可変長引数テンプレートを使うことである:
template<char...> OutputType operator""_Suffix(); OutputType someVariable = 1234_Suffix;
これにより、operator""_Suffix<'1', '2', '3', '4'>としてリテラル処理関数が実体化される。この形式では、文字列のヌル終端文字は無い。これを行う主目的は、C++0xのconstexprを使い、OutputTypeをconstexprで構築可能かつコピー可能とし、リテラル処理関数をconstexpr関数とすることで、コンパイラにリテラルの変換を完全にコンパイル時に行わせることである。
cookedリテラルの場合は、cookedリテラルの型が使われ、代替となるテンプレート形式は無い:
OutputType operator""_Suffix(int the_value); OutputType someVariable = 1234_Suffix;
文字列リテラルの場合、以下のものが使用でき、前述の新たな文字列接頭辞と組み合わせられる:
OutputType operator""_Suffix(const char * string_values, size_t num_chars); OutputType operator""_Suffix(const wchar_t * string_values, size_t num_chars); OutputType operator""_Suffix(const char16_t * string_values, size_t num_chars); OutputType operator""_Suffix(const char32_t * string_values, size_t num_chars); OutputType someVariable = "1234"_Suffix; //const char * バージョンを呼び出す OutputType someVariable = u8"1234"_Suffix; //const char * バージョンを呼び出す OutputType someVariable = L"1234"_Suffix; //const wchar_t * バージョンを呼び出す OutputType someVariable = u"1234"_Suffix; //const char16_t * バージョンを呼び出す OutputType someVariable = U"1234"_Suffix; //const char32_t * バージョンを呼び出す
文字リテラルも同様に定義できる。
[編集] マルチタスク用のメモリモデル
C++標準化委員会はマルチスレッドの標準的サポートの導入を計画している。
これには二つの側面がある。つまり、複数スレッドが一つのプログラム中で共存できるメモリモデルを定義することと、スレッド間相互作用のサポートを定義することである。二つ目に関してはライブラリによって提供される。#スレッディングを参照のこと。
複数のスレッドが同じメモリ位置にアクセスする可能性がある環境下でのプログラム記述には、メモリモデルが必要となる。つまり、ルールを遵守するプログラムは正しく実行されるであろうと思われるが、ルールに従わないプログラムはコンパイラの最適化に依存する未定義の振る舞いや、memory coherenceの問題を抱えることになる。
[編集] スレッドローカル記憶域
マルチスレッド環境においては、各スレッドごとに独立した変数が必要となることがある。このような変数は、関数でのローカル変数としては既に存在するが、グローバル変数、また静的変数としてはまだ無い。
新たなスレッドローカル記憶クラスが、(現存のstatic、動的、autoに加えて)次期標準に提案されている。スレッドローカル記憶域は、記憶クラス指定子 thread_localによって指定することになるであろう。
スレッドローカル記憶クラスは、static記憶クラスを持ち得るあらゆるオブジェクト(プログラムの実行期間全体にわたって存在しているようなもの)にも付けられるだろう。つまり、スレッドローカルなオブジェクトは、static記憶クラスの変数と同様のやり方で、コンストラクタで初期化されデストラクタで破壊されるということである。
[編集] コンパイラが生成する関数へのdefault/delete指定
現行の標準C++では、オブジェクトにコンストラクタ、コピーコンストラクタ、代入演算子 (operator =)、そしてデストラクタが与えられていない場合、コンパイラが自動的にそれらを提供する。ユーザーは自身のバージョンを定義することで、デフォルトの動作を上書きすることができる。また、C++は全てのクラスに適用されるグローバルな演算子(operator=やoperator new)も提供しており、その動作もユーザーが上書きできる。
しかし、デフォルトで作られる関数の生成を制御する方法が非常に少ないという問題がある。例えば、クラスを実質的にコピー不能にするには、コピーコンストラクタと代入演算子をprivateとして宣言し、定義しないことによって実現できる。これらの関数を使用しようとすると、コンパイラやリンカがエラーを報告するようになるというわけである。しかし、これは理想的な解法とはいえないであろう。
さらに言えば、例えばデフォルトコンストラクタなど、コンパイラに対して明示的に生成するよう伝えておくことも有用である。オブジェクトに「あらゆる」コンストラクタが定義されているのであれば、コンパイラはデフォルトコンストラクタを生成しない。また、特別なコンストラクタとコンパイラが生成するデフォルトとを両方持っておくことも有用な場面がある。
C++0xでは、これらコンパイラが生成する関数の使用・不使用を明示できるようになる予定である。例えば、以下のクラスはデフォルトコンストラクタの使用を明示している:
struct SomeType { SomeType() = default; //デフォルトコンストラクタが明示される SomeType(OtherType value); };
また逆に、明示的に無効にすることもできる。以下の例はコピー不能な型の例である:
struct NonCopyable { NonCopyable & operator=(const NonCopyable&) = delete; NonCopyable(const NonCopyable&) = delete; NonCopyable() = default; };
new演算子で割り当てられないようにすることも出来る:
struct NonNewable { void *operator new(std::size_t) = delete; };
このオブジェクトは、スタック上か、他の型のメンバとしてしか割り付けられない。移植性の無いトリックを使いでもしない限り、直接ヒープには割り当てられないのである(ユーザー割り当てのメモリ上にコンストラクタを呼ぶ方法は配置newしかなく、その使用は上記のように禁じられているので、オブジェクトは適切に構築できない)。
= deleteという指定は、どんな関数の呼び出しも禁止できる。これは、メンバ関数を特定の型で呼び出すのを禁止するのに使える。例えば:
struct NoDouble { void f(int i); void f(double) = delete; };
doubleでf()を呼ぼうとすると、暗黙のintへの変換を行うのではなく、コンパイラによってエラーになる。以下のようにすることで、int型以外の型では呼べないようにする総称化が出来る:
struct NoDouble { void f(int i); template<class T> void f(T) = delete; };
[編集] long long int型
32ビットのシステムにおいて、最低でも64ビットあるような整数型、long long intがあると便利である。C99標準では既にこの型が標準Cに導入されているし、C++用コンパイラの殆どで長期的にサポートされている拡張でもある(実際、コンパイラによってはC99で導入される前からサポートしているものもある)。C++0xでも同様に、この型が標準C++に導入される。
これは、一部の64ビットシステム上では余り有用ではない。一般的な64ビットシステム上でのデータサイズを挙げると:
- 16ビット:
short int - 32ビット:
int - 64ビット:
long int
(これはLP64と呼ばれるモデルである)
それでも、32ビットシステムや、64ビットWindows環境(LLP64モデルであり、longが32ビットである)では、64ビット整数としてlong long intを使うのが根強い。
C++委員会は、C委員会(お互いに連絡を取っており、グループに大きな重なりがあるが、C++委員会とは独立である)の決定と合わない新たな組み込み型の標準化を避けてきた。しかし今や、long long int(略してlong long)は事実上の標準であり、さらにはC99で本当の標準化もされているので、この難局も解消されるだろう。C++委員会はlong long intを組み込み型として認可する予定である(unsigned long long intも含まれる)。
将来的に、十分な要求があったり、128ビットのレジスタを持つようなプロセッサが現れたりすれば、long long intは128ビット型として使われるようになるだろう。
[編集] 静的な表明
C++標準には、表明を確認する二つの方法として、assertマクロと#errorプリプロセッサディレクティブがある。しかし、このどちらもテンプレート使用時には不適切である。assertマクロでの確認は実行時であり、#errorプリプロセッサディレクティブでの確認はプリプロセス時である。どちらもテンプレート実体化時の確認には使えないため、テンプレート引数に依存する特性の検証には適していない。
新たなキーワードであるstatic_assertを用いることで、コンパイル時点での表明確認の新たな手法が導入される。宣言は以下のような形になるであろう:
static_assert( ''constant-expression'', ''error-message'' ) ;
以下に、static_assertの使用法の例を挙げる:
static_assert(3.14 < GREEKPI && GREEKPI < 3.15, " GREEKPI is inaccurate!"); template<typename T> struct Check { static_assert(sizeof(int) <= sizeof(T), "T is not big enough!"); };
constant-expressionがfalseとなる場合、コンパイラはエラーメッセージを生成する。一つ目の例は#errorプリプロセッサディレクティブの代替の例である。二つ目の例の方は、Checkクラステンプレートの各実体化において表明の確認をしている。
静的な表明はテンプレート以外での使用も有益である。例えば、longがintより大きいサイズであることに依存するアルゴリズムの実装のように、標準では保障されていない箇所の確認などの用途がある(この場合は、longを新たな型であるlong longに置き換えるほうがもっともらしい。先程の例のような仮定はほとんどのシステム、コンパイラで正しいだろうが、全ての環境で正しいとは言えない)。
[編集] インスタンス化されていないクラスメンバへのsizeof
現行の標準C++では、sizeof演算子は型とオブジェクトに対してしか適用できない。そのため、以下のようには使用できない:
struct SomeType { OtherType member; }; sizeof(SomeType::member); //動かない
この例では、OtherTypeのサイズを取得したい所であるが、C++03ではこれは不正となりコンパイルエラーとなる。C++0xでは認められるようになる予定である。
ちなみに、C++03で同様のことをしたい場合、以下のようにすればよい:
struct SomeType { OtherType member; }; sizeof(static_cast<SomeType*>(0)->member);
[編集] C++標準ライブラリの拡張
大量の新機能が、C++0x標準ライブラリに追加されようとしている。新しいライブラリの多くは現行の標準規格で実装できるが、C++0xのコア言語の新機能に(程度の差はあれど)依存しているものもある。
導入されるライブラリの大部分は、C++ Standards Committee's Library Technical Report(TR1と呼ばれる)の文書で定義されている。TR1の最終稿は、2005年に出されている。
TR1ライブラリの実装は既に数種類が利用可能であり、std::tr1名前空間を用いて呼び出せる。C++0xではstd名前空間に移動される。
委員会は、二つ目のTechnical Report (TR2)をC++0xの標準化後に予定している。C++0xに間に合わないライブラリ提案は、TR2や、さらに先のTechnical Reportに置かれることになるだろう。
以下の提案は、C++0xに搭載されることが予定されているものである。
[編集] 標準ライブラリの改良
C++0xでは言語に多数の新機能が提供され、既存の標準ライブラリもその恩恵を受けられる。例えば、大半の標準ライブラリのコンテナは右辺値参照に基づいたムーブコンストラクタを利用して、重いコンテナを高速に移動したり、新たなメモリ位置に中身を移動することができる。標準ライブラリは必要に応じてC++0xの新機能を使って改良される予定である。それには下記のようなものが含まれるが、必ずしもこれだけに限定されるわけではない。
- コンセプト
- 右辺値参照とそれに付随するムーブのサポート
- UTF-16とUTF-32の文字型のサポート
- 可変個引数テンプレート(完全な転送を可能にするための右辺値参照と共に)
- コンパイル時の定数式
- decltype
- 明示的な変換関数
- 関数へのdefault/delete指定
加えて、C++が標準化されてから長い時間がたち、標準ライブラリを使った大量のコードが書かれてきた。その結果、改良を施すことができる標準ライブラリの部分が明らかになった。その場所の多くは標準ライブラリのアロケータである。C++0xには現在のアロケータのモデルを補うためにスコープに基づく新しいモデルが含まれる予定だ。
[編集] スレッディング
スレッドクラスにより、関数オブジェクトを取って新たなスレッドを起動することが可能になる。各時点に於いて、あるスレッドが実行中のスレッドにjoinすること(スレッドの終了を待つこと)も可能である。(可能であれば)ネイティブスレッドにアクセスし、プラットフォーム固有の操作を行うことも可能となるだろう。
スレッド間の同期のためにミューテックスや条件変数がライブラリに追加され、RAII方式のロックやロックアルゴリズムの使用も容易になる。
低レベル処理の高効率性のためには、スレッド間通信をミューテックスのオーバーヘッドなしで行う機能も必要となる。これは、適切なメモリバリアと不可分操作を用いることで達成できる。不可分操作ライブラリにより、各操作を行うのに必要な最小限の同期だけを指定することが出来る。
Futureやスレッドプールのような高レベルスレッディング用のライブラリも作業中であるが、C++0x標準には現れないであろう。基礎となるライブラリとして実装され、将来のTechnical Reportに含まれることが望まれる。
[編集] タプル型
タプルとは、決められた次元数の、異なる種類の型のオブジェクトから構成されたコレクションである。あらゆる型のオブジェクトが、タプルの要素になりうる。
この新ユーティリティは、新ヘッダとC++言語拡張を通して実装される。つまり:
- 可変長引数テンプレート
- 参照への参照
- テンプレート関数のデフォルト引数(現在はテンプレート関数にしか使えない)
以下が、<tuple>ヘッダで定義されているタプルである:
template <class... Types> class tuple;
タプル型の定義例、使用例を挙げる:
typedef tuple<int, double, long &, const char *> test_tuple; long lengthy = 12; test_tuple proof(18, 6.5, lengthy, "Ciao!"); lengthy = get<0>(proof); //'lengthy'に値18が代入される。 get<3>(proof) = " Beautiful!"; //タプルの第四要素を修正。
タプルproofを内容の定義なしに生成することが可能であるが、タプル要素の型がデフォルトコンストラクタを持つ場合に限られる。さらに、タプルを別のタプルに代入することも出来る。これが使用可能なのは、タプルの型が同じで各要素の型がコピーコンストラクタを持つ場合、もしくは右辺側のタプルの各型が左辺側のタプルのそれぞれの型と変換可能な場合、左辺側のタプルの各要素型が適切なコンストラクタを持つ場合である。
typedef tuple<int , double, string > tuple_1 t1; typedef tuple<char, short , const char *> tuple_2 t2('X', 2, "Hola!"); t1 = t2; //Ok 最初の二つの要素は変換可能で、 //三つ目の型は'const char *'から構築できる。
比較演算子も使用可能であり(タプルの要素数が同じ場合)、タプルの特性を確認する二つの式が使用可能である(コンパイル時のみ)。
tuple_size<T>::valueタプルTの要素数を返すtuple_element<I, T>::typeタプルTのI番目のオブジェクトの型を返す。
[編集] ハッシュテーブル
C++標準ライブラリへのハッシュテーブル(順序付けの無い連想コンテナ)の導入要求は、最も繰り返されてきた要求の一つである。これは、時間的な制約のみのために、現在の標準(1995年、1998年)に導入されなかった。ハッシュテーブルは最悪の場合(大量の衝突が発生)の効率は平衡木に劣るが、現実のアプリケーションでは多くの場合効率的である。
衝突の管理は、チェイン法によってのみ行われる。これは、委員会には、多くの実装上の問題(特に要素の消去が認められるような場合)があるオープンアドレス法を標準化する機会がないからである。 非標準のライブラリの持つ、独自のハッシュテーブル実装と名前が衝突しないように、"hash"の代わりに"unordered"が使用される。
ハッシュテーブルには四つの種類があり、同じキーを持つ要素を認めるかどうかと、キーに値を関連付けるかどうかで区別される。
| ハッシュ表の型 | 値の関連付け | 重複するキー |
|---|---|---|
| unordered_set | ||
| unordered_multiset | ○ | |
| unordered_map | ○ | |
| unordered_multimap | ○ | ○ |
新クラスは、コンテナクラスの要件を全て満たし、要素へのアクセスに必要な全てのメソッドを備える:insert, erase, begin, end.
この新ユーティリティは、C++言語の拡張は必要ない。<functional>ヘッダへの小さな拡張と、<unordered_set>ヘッダ、<unordered_map>ヘッダの導入を行うのみである。他の標準クラスには変更の必要がなく、標準ライブラリの他の拡張にも依存しない。
[編集] 正規表現
正規表現のために作られた、多くの非標準、準標準のライブラリがある。このようなアルゴリズムを使うのは非常に一般的なので、オブジェクト指向言語の能力を用いて、標準ライブラリにもこの機能が導入される。
新ライブラリでは、新ヘッダ<regex>が定義され、二つの新クラスが導入される:
- 正規表現を表すテンプレートクラス
basic_regex; - 結果を表すテンプレートクラス
match_results.
検索のためにregex_search関数が使われ、「検索と置換」とためにregex_replace関数が使われ、新たな文字列を返す。 アルゴリズムregex_searchとregex_replaceは正規表現と文字列を取り、結果をmatch_resultsに収める。
以下にmatch_resultsの使用例を示す:
const char *reg_esp = "[ ,.\\t\\n;:]"; //分割文字のリスト regex rgx(reg_esp); // 'regex'は'char'型を引数にした、 // 'basic_regex'テンプレートのインスタンス cmatch match; // 'cmatch'は'const char *'を引数にした、 // 'match_results'テンプレートのインスタンス const char *target = "Polytechnic University of Turin " ; // 'reg_esp'の文字で分割された、'target'の全単語を分別 if (regex_search(target, match, rgx)) { // 単語が特定の文字で分割されていた場合は表示 for (int a = 0; a < match.size(); ++a) { string str( match[a].first, match[a].second ) ; cout << str << "\n" ; } }
注意: C++プリプロセッサはバックスラッシュをエスケープ文字として扱うため、例ではバックスラッシュを二つ重ねている。C++0xのraw文字列機能により、この問題は回避できる。
regexライブラリは、既存のヘッダの変更や言語拡張を必要としない。
[編集] 一般用途のスマートポインタ
動的メモリ確保は、プログラミング言語の歴史においても昔からの論点である。多くの現代的プログラミング言語(Javaなど)は、自動メモリ管理の仕組みを備えている。
C++のポインタには、多くの興味深い性質がある:
- コピーできる。
- 代入できる。
- 値を使うことが出来る。
void *を総称的ポインタとして使える。- 静的キャストで、基底クラスのポインタに変換できる。
- 動的キャストで、派生クラスのポインタに変換できる。
C++のポインタの主要な欠点は:
- 動的に割り当てられたメモリを手動管理する責任がある。
- メモリ上の、不正なアドレスや未割り当てのアドレスが参照できる。
新たなスマートポインタは、通常のポインタの利点を維持したまま、弱点を大幅に削ったものである。新テンプレートクラスshared_ptrは、現在の標準C++ライブラリに在るauto_ptrに似ているが、同じオブジェクトに対する「同等の」参照をカウントする機能が追加され、関連するスマートポインタ間でオブジェクトの所有権を共有する。さらに、shared_ptrは、標準コンテナと一緒に用いることも出来る。
同じオブジェクトを参照するポインタの数を得るには、shared_ptrのメンバ関数であるuse_countが使えるだろう。メンバ関数resetは、スマートポインタをリセットする。リセットされたポインタは「空」になり、use_countを呼び出すとゼロを返す。
以下に、shared_ptrの使用例を示す:
int main( ) { shared_ptr<double> p_first(new double); { shared_ptr<double> p_copy = p_first; *p_copy = 21.2; } // 'p_copy'は破壊されるが、割り当てられたdoubleは破壊されない return 0; // 'p_first'が破壊され、同時に割り当てられたdoubleも破壊される }
これと関連し、弱い参照としてweak_ptrテンプレートが提案されている。これもスマートポインタ・参照と同様に振舞うが、shared_ptrとの違いは、参照先の値がポインタによって「所有」されず、弱参照ポインタが存在していても破壊され得る点である。これは、弱参照ポインタには参照先オブジェクトが破壊されてしまうことによる実行時エラーが発生しやすいことを意味している。しかしこれにより、オブジェクトの生存時間に影響しない形で参照を持つことが可能となり、循環参照を防止するようなことができる。
このユーティリティは<memory>ヘッダに変更を必要とするが、C++言語拡張は必要としない。
また、unique_ptrがauto_ptrの代わりとして導入され、auto_ptrは非推奨とされる。unique_ptrはauto_ptrの全機能を提供するが、左辺値からの暗黙的な移動の際に起こる挙動が異なる。unique_ptrは、C++0xのMove Semanticsを用いたコンテナになっている。
[編集] 拡張可能な乱数の枠組み
コンピュータは決定的な振る舞いをするものであるが、乱数列を生成することにより、(外見だけでも)非決定的な振る舞いが必要となるようなアプリケーションがある。
標準に用意されている方法は、rand関数のみである。しかし、十分な定義がされていないため、そのアルゴリズムは完全にライブラリ開発者に任せられている。新たな乱数生成ユーティリティは<random>ヘッダを通して定義される。他のヘッダやC++言語への修正は必要としない。
乱数生成器は、内部状態と、結果を計算し次の状態を生成するための関数をもつ。この二つの特性によって、乱数生成エンジンが定まる。他の特性として、生成結果や乱数の間隔や密度の分布も重要である。
テンプレートクラスvariate_generatorを通して、希望するエンジンと分布を持った乱数生成器を生成することが可能である。標準やユーザ設計のエンジン・分布から選ぶことが出来る。
- 擬似乱数エンジン
新ライブラリには、数種の擬似乱数生成エンジンが導入される。これらはテンプレートクラスであり、必要に応じて個別化することが出来る。 擬似乱数エンジンの内部状態は種(一般には変数の集合)によって決定される。
| テンプレートクラス | int/float | 質 | 速度 | 内部状態の大きさ(※) |
|---|---|---|---|---|
| linear_congruential | int | 低 | 中 | 1 |
| substract_with_carry | 両方 | 中 | 遅い | 25 |
| mersenne_twister | int | 良 | 速い | 624 |
※使われる型の次元数・バイト数倍される。
エンジンの質はテンプレートクラスdiscard_blockを用いて向上でき、テンプレートクラスxor_combineと組み合わせることも出来る。利便性のため、<random>ヘッダには数種の標準エンジンが定義されている。例として、mersenne_twisterから実体化されるmt19937クラスを示す。
typedef mersenne_twister<''implementation-defined'', 32, 624, 397, 31, 0x9908b0df, 11, 7, 0x9d2c5680, 15, 0xefc60000, 18> mt19937 ;
- 非決定的乱数エンジン
random_deviceクラスを用いて、unsigned int型の非決定的乱数を生成できる。実装には、システムから独立した入力を持つデバイス(例えば、非同期的な外部カウンタや、特殊なトランスデューサーなど)の使用や、伝統的な「結果を調節する」擬似乱数エンジンの使用が必要である。
- 乱数分布
新ライブラリには、一様分布から確率論で定義された分布まで、多種類の分布が定義されている: uniform_int、 bernoulli_distribution、 geometric_distribution、 poisson_distribution、 binomial_distribution、 uniform_real、 exponential_distribution、 normal_distribution、 gamma_distributionがある。明らかであるが、標準の分布を実体化しても構わないし、ユーザ自身の互換性のある分布を用いても構わない。
以下は、新ライブラリを用いた簡単な例 [1] である:
uniform_int<int> distribution( 0, 99 ) ; mt19937 engine ; variate_generator<mt19937, uniform_int<int>> generator( engine, distribution ); int random = generator() ; // 0から99の値を代入
[編集] 参照ラッパ
参照ラッパはテンプレートクラスreference_wrapperのインスタンスとして得られる。参照ラッパは、C++言語の通常のリファレンス ('&')にも似ている。オブジェクトから参照ラッパを得るには、テンプレート関数refを使う(constな参照の場合はcrefが使用される)。
参照ラッパはテンプレート関数と使用する場合に有用であり、特に引数としてコピーではなく参照を取る必要がある場合に便利である:
// 引数'r'への参照を取り、それを増加させる関数 void f(int &r) { ++r; } // テンプレート関数 template<class F, class P> void g(F f, P t) { f(t); } int main() { int i = 0; g(f, i); //g<void (int &r), int>が実体化される。 //iは修正されない。 cout << i << endl; // 0が出力される g(f, ref(i)) ; //g<void(int &r),reference_wrapper<int>>が実体化される。 //よってiの値が修正される。 cout << i << endl; // 1が出力される }
この新ユーティリティは現在の<utility>に追加される。C++言語の拡張は必要としない。
[編集] 関数オブジェクトの多相的ラッパ
関数オブジェクトの多相的ラッパ(「多態的関数オブジェクトラッパ」とも呼ばれる)は、意味的にも構文的にも関数ポインタに似ている。しかし、引数がラッパの引数型と変換可能である関数を参照できる点など、束縛が緩和されている。
次の例で、特性を理解できるだろう:
function<int (int, int)> pF; //テンプレートクラスfunctionを用いて //ラッパを生成する。 plus<int> add; // plusはtemplate<class T> T plus(T, T);として宣言されている。 //そのため、addの型はint add(int x, int y)。 pF = &add ; //引数型が一致しているため、代入可能。 int a = pF( 1, 2 ) ; //この時、ラッパpFが関数を参照していない場合、 //[[例外処理|例外]]bad_function_callが投げられる。 function<bool (short, short)> pg; if (pg == NULL) // pgには関数が代入されていないので、常に成立。 { bool adjacent(long x, long y); pg = &adjacent; //引数の型・戻り値の型が変換可能なので、代入できる。 struct test { bool operator ()(short x, short y); } car; pg = ref(car); // refは、car構造体のメンバ関数operator ()の //参照を返すテンプレート関数。 } pF = pg; //ラッパpgのとラッパpFの引数の型・戻り値の型が //それぞれ変換可能なので、正当。
テンプレートクラスfunctionは<functional>ヘッダで宣言される。C++言語への変更は必要としない。
[編集] メタプログラミングのための型特性
メタプログラミングとは、他のプログラム(もしくは自分自身)を生成、修正するプログラムを生成することである。これらの動作は、コンパイル時、もしくは実行時に発生する。C++標準化委員会は、テンプレートを通したコンパイル時メタプログラミングを可能にするライブラリの導入を決定した。
以下は、現在の標準でのメタプログラミングの例である: 指数計算に、テンプレート実体化の再帰を用いている。
template<int B, int N> struct Pow { // 再帰呼び出しと再結合 static const int value = B * Pow<B, N - 1>::value; } ; template<int B> struct Pow<B, 0> // 終了条件、''N == 0'' { static const int value = 1; }; int quartic_of_three = Pow<3, 4>::value;
多くのアルゴリズムは、型の違うデータに適用可能である。C++のテンプレートはジェネリックプログラミングをサポートし、より小さく有用なコードを作れる。それでも、アルゴリズムは使用されている型の情報を必要とすることも多い。この種の情報は、型特性を使用してテンプレートクラスの実体化時に展開できる。
型特性は、オブジェクトの分類やクラス(または構造体)の性質を示すものである。新ヘッダの<type_traits>で宣言される。
次の例は、テンプレート関数elaborateの例である。この関数は、渡されたデータ型に基づき、二つのアルゴリズム (algorithm.do_it)から一方を実体化する:
// 一つ目の動作 template<bool B> struct algorithm { template< class T1, class T2 > int do_it(T1 &, T2 &) { /*...*/ } }; // 二つ目の動作 template<> struct algorithm<true> { template<class T1, class T2> int do_it()(T1 *, T2 *) { /*...*/ } }; // elaborateの実体化の際に、自動的に正しい動作をする方が実体化される。 template<class T1, class T2> int elaborate(T1 A, T2 B) { // T1が整数で、T2が浮動小数の時に一つ目の動作を行う。 // 他の場合は二つ目の動作を行う。 return algorithm<is_integral<T1>::value && is_floating_point<T2>::value>::do_it(A, B); }
ヘッダ<type_transform>で定義された型特性を用いると、型変換の処理を生成することも出来る(static_castとconst_castはテンプレート中では不十分)。
この種のプログラミングで、美しく簡潔なコードを書ける。しかし、このテクニックの弱点はデバッグである。コンパイル時のデバッグは不適切であるし、プログラム実行時には非常に難しい。
[編集] 関数の戻り値型を算出する、一様な手法
テンプレート関数オブジェクトの戻り値の型を(コンパイル時に)決定するのに、直感的な方向がない。特に引数の型に基づいて決まる場合は顕著である。
例として以下のコードを挙げる:
struct clear { int operator ()(int ); //引数の型と戻り値の型が同じ double operator ()(double); }; template<class Obj> class calculus { public: template<class Arg> Arg operator ()(Arg& a) const { return member(a); } private: Obj member; };
calculusクラステンプレートをclearクラスで実体化する場合(calculus<clear>として実体化する場合)、calculus関数オブジェクトはclear関数オブジェクトと同じ戻り値型を持つ。
ここで、calculusクラステンプレートをconfusedクラスで実体化する場合(calculus<confused>として実体化する場合)を考えよう:
struct confused { double operator ()(int ); //引数の型と戻り値の型が違う int operator ()(double); };
calculusの戻り値型はconfusedクラスと同じではない(calculus<confused>.operator()の実体化に伴い、intから doubleへの変換や逆変換が起こる)。
次期標準に提案されている新ライブラリで、result_ofテンプレートクラスが導入され、各宣言ごとに関数オブジェクトの戻り値型の決定と使用が可能になる。これを用いて、関数オブジェクトの戻り値型を取得するようにすることでcalculusを修正した:
template<class Obj> class calculus_ver2 { public: template<class Arg> typename result_of<Obj(Arg)>::type operator()(Arg& a) const { return member(a); } private: Obj member; };
このようにすることで、関数オブジェクトcalculus_ver2<confused>の実体化の際に、型変換は発生しない。
関数オブジェクト呼び出し時の戻り値型の決定は、式の戻り値型を決定するという一般的な問題の一部である。将来的には、問題の起き得る箇所にdecltypeのような機能を使うことで解決されるだろう。
[編集] 脚注
- ^ このコードは新ライブラリを用いる手法を例示したものであり、実際にはmt19937をエンジンとして[0-99]を生成する為にuniform_intを用いるメリットは無い。この場合は古典的な手法(X % A + B)を用いるのが効率的であり、distributionの利用は、大きな値の範囲やengineが直接生成不能な数を扱う場合、正規分布など特定の分布持った乱数を生成したい場合等に有効である。
[編集] 関連項目
[編集] 参考文献
[編集] C++標準化委員会の文書
- ISO/IEC DTR 19768 (October 23, 2007) Doc No: N2432 State of C++ Evolution (post-Kona 2007 Meeting)
- ISO/IEC DTR 19768 (August 7, 2007) Doc No: N2389 State of C++ Evolution (pre-Kona 2007 Meetings)
- ISO/IEC DTR 19768 (July 29, 2007) Doc No: N2336 State of C++ Evolution (Toronto 2007 Meetings)
- ISO/IEC DTR 19768 (June 25, 2007) Doc No: N2291 State of C++ Evolution (Toronto 2007 Meetings)
- ISO/IEC DTR 19768 (May 3, 2007) Doc No: N2228 State of C++ Evolution (Oxford 2007 Meetings)
- ISO/IEC DTR 19768 (January 12, 2007) Doc No: N2142 State of C++ Evolution (between Portland and Oxford 2007 Meetings)
- ISO/IEC DTR 19768 (October 22, 2007) Doc No: N2461 Working Draft, Standard for programming Language C++
- ISO/IEC DTR 19768 (June 24, 2005) Doc No: N1836 Draft Technical Report on C++ Library Extensions
- Lawrence Crowl (May 2, 2005) Doc No: N1815 ISO C++ Strategic Plan for Multithreading
- Detlef Vollmann (June 24, 2005) Doc No: N1834 A Pleading for Reasonable Parallel Processing Support in C++
- Lawrence Crowl (May 2, 2007) Doc No: N2280 Thread-Local Storage
- Jan Kristoffersen (October 21, 2002) Doc No: N1401 Atomic operations with multi-threaded environments
- Hans Boehm, Nick Maclaren (April 21, 2002) Doc No: N2016 Should volatile Acquire Atomicity and Thread Visibility Semantics?
- Lois Goldthwaite (October 5, 2007) Doc No: N2437 Explicit Conversion Operators
- Francis Glassborow, Lois Goldthwaite (November 5, 2004) Doc No: N1717 explicit class and default definitions
- Bjarne Stroustrup, Gabriel Dos Reis (December 11, 2005) Doc No: N1919 Initializer lists
- Herb Sutter, Francis Glassborow (April 6, 2006) Doc No: N1986 Delegating Constructors (revision 3)
- Michel Michaud, Michael Wong (October 6, 2004) Doc No: N1898 Forwarding and inherited constructors
- Bronek Kozicki (September 9, 2004) Doc No: N1676 Non-member overloaded copy assignment operator
- R. Klarer, J. Maddock, B. Dawes, H. Hinnant (October 20, 2004) Doc No: N1720 Proposal to Add Static Assertions to the Core Language (Revision 3)
- V Samko; J Willcock, J Jarvi, D Gregor, A Lumsdaine (February 26, 2006) Doc No: N1968 Lambda expressions and closures for C++
- J. Jarvi, B. Stroustrup, D. Gregor, J. Siek, G. Dos Reis (September 12, 2004) Doc No: N1705 Decltype (and auto)
- B. Stroustrup, G. Dos Reis, Mat Marcus, Walter E. Brown, Herb Sutter (April 7, 2003) Doc No: N1449 Proposal to add template aliases to C++
- Douglas Gregor, Jaakko Jarvi, Gary Powell (September 10, 2004) Doc No: N1704 Variadic Templates: Exploring the Design Space
- Gabriel Dos Reis, Bjarne Stroustrup (October 20, 2005) Doc No: N1886 Specifying C++ concepts
- Daveed Vandevoorde (January 14, 2005) Doc No: N1757 Right Angle Brackets (Revision 2)
- Walter E. Brown (October 18, 2005) Doc No: N1891 Progress toward Opaque Typedefs for C++0X
- J. Stephen Adamczyk (April 29, 2005) Doc No: N1811 Adding the long long type to C++ (Revision 3)
- Chris Uzdavinis, Alisdair Meredith (August 29, 2005) Doc No: N1827 An Explicit Override Syntax for C++
- Herb Sutter, David E. Miller (October 21, 2004) Doc No: N1719 Strongly Typed Enums (revision 1)
- Matthew Austern (April 9, 2003) Doc No: N1456 A Proposal to Add Hash Tables to the Standard Library (revision 4)
- Doug Gregor (November 8, 2002) Doc No: N1403 Proposal for adding tuple types into the standard library
- John Maddock (March 3, 2003) Doc No: N1429 A Proposal to add Regular Expression to the Standard Library
- P. Dimov, B. Dawes, G. Colvin (March 27, 2003) Doc No: N1450 A Proposal to Add General Purpose Smart Pointers to the Library Technical Report (Revision 1)
- Doug Gregor (October 22, 2002) Doc No: N1402 A Proposal to add a Polymorphic Function Object Wrapper to the Standard Library
- D. Gregor, P. Dimov (April 9, 2003) Doc No: N1453 A proposal to add a reference wrapper to the standard library (revision 1)
- John Maddock (March 3, 2003) Doc No: N1424 A Proposal to add Type Traits to the Standard Library
- Daveed Vandevoorde (April 18, 2003) Doc No: N1471 Reflective Metaprogramming in C++
- Jens Maurer (April 10, 2003) Doc No: N1452 A Proposal to Add an Extensible Random Number Facility to the Standard Library (Revision 2)
- Walter E. Brown (October 28, 2003) Doc No: N1542 A Proposal to Add Mathematical Special Functions to the C++ Standard Library (version 3)
- Douglas Gregor, P. Dimov (April 9, 2003) Doc No: N1454 A uniform method for computing function object return types (revision 1)
[編集] 記事
- a b The C++ Source Bjarne Stroustrup (January 2, 2006) A Brief Look at C++0x
- ^ C/C++ Users Journal Bjarne Stroustrup (May, 2005) The Design of C++0x: Reinforcing C++’s proven strengths, while moving into the future
- Web Log di Raffaele Rialdi (September 16, 2005) Il futuro di C++ raccontato da Herb Sutter
- Informit.com (August 5, 2006) The Explicit Conversion Operators Proposal
- Informit.com (July 25, 2006) Introducing the Lambda Library
- Dr. Dobb's Portal Pete Becker (April 11, 2006) Regular Expressions TR1's regex implementation
- Informit.com (July 25, 2006) The Type Traits Library
- Dr. Dobb's Portal Pete Becker (May 11, 2005) C++ Function Objects in TR1
[編集] 外部リンク
- The C++ Standards Committee
- Bjarne Stroustrup's homepage
- C++0X: The New Face of Standard C++
- Herb Sutter's blog coverage of C++0X
- A talk on C++0x given by Bjarne Stroustrup at the University of Waterloo
- A quick and dirty introduction to C++0x (as of November 2007)

