コンテンツにスキップ

英文维基 | 中文维基 | 日文维基 | 草榴社区

C++11

出典: フリー百科事典『ウィキペディア(Wikipedia)』
C++1xから転送)
C++ > C++11

C++11は、プログラミング言語 C++ISO標準 ISO/IEC 14882:2011 の略称である。規格の策定中は2009年中の標準化を目指していたため、C++0x という仮称で呼ばれていた。 ISO/IEC 14882:2003 (C++03) に代わるものとして、2011年8月12日にISOによって承認された[4]。後継のC++14が2014年8月18日に承認されている。

コア言語への機能追加や標準C++ライブラリの拡張を施し、C++TR1ライブラリの大部分を(数学的特殊関数ライブラリを除いて)取り込んでいる。

標準策定の方針

[編集]

C++ への修正はコア言語と標準ライブラリの双方に及ぶ。

委員会は、新規格の個別の要素の策定に際して次のような方針をとった。

  • C++98 との、さらに可能であれば C との一貫性および互換性を維持すること。
  • 新機能の実現方法として、コア言語の拡張よりも標準ライブラリの拡張を優先すること。
  • プログラミングの技法を発展させうるような変更を優先すること。
  • 特定のアプリケーションにのみ有効な機能を導入するよりも、システムやライブラリの設計が容易になるような改良を行うこと。
  • 従来の型安全でない技法に対して、より安全な代替を提供すること。
  • ハードウェアと密接に動作する能力と効率を向上すること。
  • 現実的問題に対する適切な解決法を用意すること。
  • “ゼロ・オーバーヘッド原則” (ある機能を使用するためのサポートは、その機能を使用しない場合は影響を及ぼさない) を実践すること。
  • 上級者向けの機能を削ることなく、C++の学習や指導が簡単になるようにすること。

初心者はプログラマ人口の多くを占める。また、多くの初心者は自身が習得した一部の言語機能に拘泥しがちであり、知識を広げようとはしない。従って、初心者への配慮は重要であると考えられた[1]。また、C++の言語仕様の大きさ(アプリケーションやプログラミング・スタイルの多様性も含む)を考えると、どれだけ長い経験を積んだプログラマも新しいプログラミングパラダイムの前では未経験者になり得ることから重要な配慮であると言える。

C++ コア言語への拡張

[編集]

C++ コア言語の特筆すべき改良点には、マルチスレッドのサポートや、ジェネリックプログラミングのサポート、一様な初期化構文やパフォーマンス向上等が挙げられる。

このページでは、コア言語の機能拡張や変更点を、実行時パフォーマンス向上、ビルド時パフォーマンス向上、使いやすさの向上、全く新しい機能の4つに分けて説明する。機能によっては複数の項目に該当するが、最もよく当てはまる項目で述べることとする。

コア言語の実行時パフォーマンス向上

[編集]

以下の機能は、主に何らかのパフォーマンス向上を狙ったものである。スピードの向上とメモリ効率の改善の両方が含まれる。

右辺値参照とムーブコンストラクタ

[編集]

C++03以前は、一時変数 (代入演算子の右側に置かれることから、右辺値と呼ばれる) に変更を加えるのは意味がないものと考えられており、関数に参照として渡す場合にはconst T&型としてしか渡すことができなかった。しかし、場合によっては変更できるほうが都合のよいこともあった。例えば所有権の移動(ムーブ)である。

C++11 では、右辺値参照と呼ばれる新たな参照型 T&& が追加された。これにより、右辺値を変更可能なまま関数に渡すことができ、右辺値からのムーブを実現できる。

例えば、std::vector は内部的には C スタイル配列のサイズ付きのラッパである。従来は、std::vector の一時変数が生成されたとき、新たな std::vector を生成してそこに全ての右辺値データをコピーしないといけなかった。コピーの後、一時変数は破壊され、内容は削除される。

右辺値参照があれば、std::vector への右辺値参照を取る std::vector の「ムーブコンストラクタ」を用いることで、単に右辺値から配列へのポインタを取り出してコピーし、空のオブジェクトを残す、ということが実現できる。この場合、配列のコピーは起こらず、空の一時変数を破壊してもメモリの破壊は起こらない。仮にstd::vector にムーブコンストラクタがない場合、通常通りにコピーコンストラクタが const std::vector<>& として呼ばれる。ムーブコンストラクタがある場合、ムーブコンストラクタが呼ばれ、メモリの割り当てが回避できる。

標準ライブラリにムーブコンストラクタが記述されていれば、既存のコードは変更を加えることなく右辺値参照のメリットを享受することができる。std::vectorの一時変数を返す関数は、明示的にstd::vector&&に変更する必要はない。なぜなら、一時変数は自動的に右辺値であるとみなされるからである。

安全上の理由から、右辺値参照として宣言された名前つきの変数をそのまま右辺値として関数に渡すことはできない(そのような変数は左辺値となる)。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++03 には既に定数式が存在している。定数式とは、3 + 4 のように、常に同じ結果を返し副作用の無いものである。定数式はコンパイラの最適化の対象となり、多くの場合コンパイル時に演算が行われ、プログラム中にはその結果のみが格納される。また、C++ の仕様中でも、多くの箇所で定数式の使用が必要となる。配列の定義にも要素数として定数式が必要であるし、列挙子の値にも必要である。

しかし、関数呼び出しやオブジェクトコンストラクタが出現すると、定数式ではなくなる。単純な例を挙げると:

int GetFive() { return 5; }

int some_value[GetFive() + 5]; // 10 要素の整数配列を作りたいが、C++03 では不正。

GetFive() + 5 が定数式でないため、これは C++03 では不正となる。実際には GetFive は実行時に一定値を返すが、コンパイラにそれを知らせる方法がないのである。理論上、関数はグローバル変数に影響を与える、実行時に結果が変わる他の関数を呼ぶ、などの理由がある。

C++11 では、キーワード constexpr が導入される。これにより、関数やオブジェクトコンストラクタがコンパイル時定数であることを保証できる。上の例は、以下のように書き直せる:

constexpr int GetFive() { return 5; }

int some_value[GetFive() + 5]; // 10 要素の整数配列を作る。C++11 では正しい。

これによりコンパイラは、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++03では、構造体が Plain Old Data (POD) 型として扱われるためにはいくつかのルールに従う必要がある。これを満たす型は C と互換性のあるオブジェクトレイアウトを生成する。しかし、C++03 におけるルールは必要以上に厳しく、より多くの型をPOD型にしてもよいのではないかという指摘があった。

C++11 では POD の定義に関して、いくつかのルールが緩和されている。

クラス・構造体は、それが trivial であり、standard-layout であり、すべての非静的データメンバがPOD型であるとき、POD型とみなされる。

trivial なクラス・構造体は、以下のように定義される。

  1. デフォルトコンストラクタを持ち、かつ、非 trivial なデフォルトコンストラクタを持たない。
  2. 非 trivial なコピーコンストラクタを持たない。
  3. 非 trivial なムーブコンストラクタを持たない。
  4. 非 trivial なコピー代入演算子を持たない。
  5. 非 trivial なムーブ代入演算子を持たない。
  6. trivial なデストラクタを持つ。

standard-layout なクラス・構造体は、以下のように定義される。

  1. 全ての非静的データメンバが standard-layout 型である。
  2. 全ての非静的データメンバに、同じアクセス制御 (public, private, protected) がかかっている。
  3. 仮想関数を持たない。
  4. 仮想基底クラスを持たない。
  5. 全ての基底クラスが standard-layout 型である。
  6. 一つ目に定義された非静的データメンバと同じ型の基底クラスを持たない。
  7. 非静的データメンバを持つ基底クラスを持たない。もしくは、導出クラスが非静的データメンバを持たず、高々一つの基底クラスしか非静的データメンバを持たない。これはつまり、クラス階層において非静的データメンバを持ってよいクラスは一つだけである、ということである。

コア言語のビルド時パフォーマンス向上

[編集]

外部テンプレート

[編集]

C++03では、ある翻訳単位で完全に引数が特定されたテンプレートが見つかったとき、コンパイラは常にそのテンプレートを実体化しなければならない。このことはコンパイルの時間を劇的に増加させる。特に、同じパラメータを用いたテンプレートが複数の翻訳単位で実体化されるときは顕著である。そして、C++03 にはそのようなテンプレートの実体化を止めさせる手段もなかった。C++11 では、これに対して外部テンプレートが導入された(これは外部変数に対するアナロジーである)。

C++03 には、特定の場所での実体化を強制的にコンパイラに命じる構文がある。

template class std::vector<MyClass>;

C++11 では、以下のような構文が導入された。

extern template class std::vector<MyClass>;

これにより、コンパイラはこの翻訳単位ではテンプレートの実体化をしないようになる。

コア言語の使いやすさの向上

[編集]

以下の機能は、主に言語を使いやすくするためのものである。例えば型安全性や、類似したコードの繰り返しを削減すること、間違ったコードが書かれにくくすることなどが含まれる。

初期化子リスト

[編集]

C++03では、初期化子リスト (initializer list) の考え方を C から引き継いでいる。つまり、構造体のメンバ定義の順に、引数を波括弧{ }の中に記述するというものである。初期化子リストは再帰的に適用されるので、構造体の配列や構造体を含む構造体にも使用できる。

struct Object {
  float first;
  int second;
};

Object scalar = {0.43f, 10}; // first = 0.43f, second = 10 であるような、1つの Object インスタンス。
Object anArray[] = {{13.4f, 3}, {43.28f, 29}, {5.934f, 17}}; // 3つの Object インスタンスから成る配列。

この初期化子リストは、静的なデータの初期化や構造体を特定の値に初期化したいときなどに有用である。C++ にはコンストラクタもあるが、初期化子リストの方が有用である。しかし、C++03 では初期化子リストは Plain Old Data (POD) 型と認識された構造体・クラスにしか適用できなかった。std::vector などの非 POD クラスには初期化子リストは使えなかった。

C++11 では、初期化子リストの考え方がテンプレートと結び付けられた。これには、std::initializer_list を用いる。これによって、コンストラクタなどの関数は、初期化子リストを引数として取ることができる。

class SequenceClass {
public:
  SequenceClass(std::initializer_list<int> list);
};

これにより、SequenceClass は、整数の列から構築できるようになる。

SequenceClass someVar = { 1, 4, 5, 6 };

このコンストラクタは、初期化子リストコンストラクタと呼ばれる特殊なコンストラクタである。このコンストラクタを持つクラスは、統一的な初期化構文の適用の際に特別に扱われる(後述)。

std::initializer_list<> クラスは、C++11 標準ライブラリのファーストクラスの型である。しかし、これを構築できるのは、{ } 構文を用いてコンパイラが静的に構築する場合だけである。一度構築されたらその後コピーはできず参照渡しするしかない。初期化リストは定数であり、一度構築されたらそのメンバを変更することはできず、メンバ中のデータも変更できない。

std::initializer_list は実際の型であるため、クラスコンストラクタのみならずそれ以外の場所でも使用できる。例えば、一般の関数の引数とすることもできる。

void FunctionName(std::initializer_list<float> list);
 
FunctionName({ 1.0f, -3.45f, -0.4f });

統一的な初期化構文

[編集]

C++03 には、型の初期化に関して多くの問題点があった。初期化に複数の方法があり、しかもお互いを取り替えたときに同じ結果になるわけではなかった。例えば、従来のコンストラクタの構文は関数宣言と同じ形をしており、場合によってはコンパイラが正しく判定できないことがあった。また、初期化リストは集約型やPOD型にしか使うことができなかった。

C++11 では、どんなオブジェクトでも初期化できる完全に統一的な記法 (uniform initialization) が導入された。これは初期化リスト構文の拡張である。

struct BasicStruct {
  int x;
  double y;
};

struct AltStruct {
  AltStruct(int x, double y) : x_(x), y_(y) {}
  
private:
  int x_;
  double y_;
};

BasicStruct var1{ 5, 3.2 };
AltStruct var2{ 2, 4.3 };

var1 の初期化は完全に C 形式の初期化子リストと同様に振る舞う。すなわち、各データメンバーは、初期化子リストのそれぞれの値によってコピー初期化される。必要ならば暗黙の型変換が行われ、型変換が不可能ならば、コンパイルに失敗する。

var2 の初期化は、単純にコンストラクタを呼ぶだけである。

型が明らかな場合、次のように書くこともできる。

struct IdString {
  std::string name;
  int identifier;
};

IdString get_string()
{
  return { "SomeName", 4 }; // 明示的な型の指定は不要。
}

統一的な初期化構文は、必ずしもコンストラクタ構文の代用となるものではない。コンストラクタ構文が必要な場合もある。クラスが初期化子リストコンストラクタ (TypeName(std::initializer_list<SomeType>)) を持つような場合、(初期化子リストがシーケンスコンストラクタの型に適合するのならば)初期化子リストコンストラクタが優先して適用される。例えば、C++11 の std::vector は、そのテンプレート型の初期化子リストコンストラクタを持つ。つまり、

std::vector<int> theVec { 4 };

のように書いた場合、初期化子リストコンストラクタが呼ばれる。固定長の std::vector を生成するための、サイズを指定する引数ひとつを取るコンストラクタstd::vector(size_type n)を呼ぶためには、標準のコンストラクタ構文を使う必要がある。

std::vector<int> theVec ( 4 );

型推論

[編集]

C++03やCでは、変数の型は使用に際して明示的に指定しなければならない。しかし、テンプレート型やテンプレートメタプログラミングなどにおいては、特に関数の戻り値型が複雑に定義されているような場合、型を簡単に書き下せないことがある。そのような場合には、中間結果を変数に格納することが難しく、メタプログラミングライブラリの内部仕様を知っていなくてはならなくなる場合もある。

C++11 では型推論により、この制約を軽減する方法が二つ提供された。一つ目の方法は、明示的に初期化される変数の定義に auto キーワードを使う方法である。これにより、初期化子によって変数の型が特定される。

auto someStrangeCallableType = boost::bind(&SomeFunction, _2, _1, someObject);
auto otherVariable = 5;

someStrangeCallableType の型は、関数テンプレート boost::bind がその引数に応じて返す戻り値の型である。これはコンパイラにとっては容易に判別できるが、ユーザーが調べるのは困難である。

otherVariable の型はまだ分かりやすい。これは、整数リテラルの型である int となる。

二つ目の方法は、キーワード decltype である。これによって、オペランドで指定した式の型をコンパイル時に取得することができる。

int someInt;
decltype(someInt) otherIntegerVariable = 5;

auto で宣言した変数の型はコンパイラにしか分からないので、decltypeauto と組み合わせて使うと特に有用である。

auto はコードの冗長性を省くのにも有用である。例えば、以下のように書かれている場合、

for (std::vector<int>::const_iterator itr = myvec.begin(); itr != myvec.end(); ++itr)

autoを使えばもっと短く書くことができる。なお、vector::cbegin()vector::cend()は、C++11で追加されたメンバー関数であり、const_iteratorを返すことが規定されている。

for (auto itr = myvec.cbegin(); itr != myvec.cend(); ++itr)

コンテナをネストして使うような場合、違いはもっと大きくなる。一方、そのような場合は typedef を使うこともできる。

範囲に基づく for ループ

[編集]

C++03では、コンテナの要素を列挙するためには多くのコードを書く必要があった。一方C#Javaのような言語には、コンテナの最初から最後までをたどるシンプルな「foreach文」が備わっている。

C++11にも同様の機能が追加された。以下のようなfor文の別表現を使ってコンテナの要素を簡単に列挙することができる。

int my_array[5] = {1, 2, 3, 4, 5};
for (int& x : my_array) {
    x *= 2;
}

この形式のfor文は、範囲に基づくfor (range-based for) と呼ばれる。この形式は、Cスタイルの配列や、初期化リスト、イテレータを返すbegin()関数とend()関数が定義されたあらゆる型に対して使うことができる。begin()end()を持つ標準ライブラリのコンテナはすべてこの形式で列挙できる。

ただし、ループの途中でイテレータを無効化することがあるケースでは、range-based forを使用することはできない[1]

ラムダ関数とラムダ式

[編集]

C++ 標準では、特に std::sort()std::find() といった C++ 標準ライブラリのアルゴリズム関数と組み合わせた時に、アルゴリズム関数の呼び出しの近くで述語関数を定義したいと思う機会が多い。しかし、このためには関数内部でクラスを定義するしかなかった。この方法は記述量が多く、またコードの流れを妨げがちである。加えて、標準 C++ の規則では、関数の中で定義されたクラスをテンプレートの中で使うことを認めていないので、結局どうしても使うことは不可能であった。

これに対して、C++11ではラムダ関数が定義できるようになった。

ラムダ関数は以下のように定義される:

[](int x, int y) -> int { int z = x + y; return z + x; }

これは、int型の引数を二つとり、int型の値を返すラムダ式である。その内容は{ } の中に記述される。 ラムダ関数の内容が "return " の形式である場合には、戻り値型を省略することができる。

[](int x, int y) { return x + y; }

このラムダ関数の戻り値型は decltype(x+y) になる。

ラムダ関数が値を返さない場合、つまり戻り値型が 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++11 標準の) std::function オブジェクトに格納するなどして、ラムダ関数を生成したスコープの外側で使いたい場合は、ラムダ関数がスタック変数を参照していないことをよく確認する必要がある。

ラムダ関数がその定義のあるスコープの内側で実行されることが保証されている場合は、次のように記述すれば、スタック変数をひとつひとつ指定せずに全ての利用可能なスタック変数を使用できる。

std::vector<int> someList;
int total = 0;
std::for_each(someList.begin(), someList.end(), [&](int x) {
  total += x;
});

[ ]の中には次のような指定が可能である。

[]        // ラムダ関数外のどの変数も使うことができない。
[x, &y]   // xはコピーされる。yは参照渡しされる。
[&]       // すべての外部変数は、もし使われれば暗黙的に参照渡しされる。
[=]       // すべての外部変数は、もし使われれば暗黙的にコピーされる。
[&, x]    // xは明示的にコピーされる。その他の変数は参照渡しされる。
[=, &z]   // zは明示的に参照渡しされる。その他の変数はコピーされる。

また、ラムダ関数がクラスのメンバ関数により定義された場合、そのクラスのフレンドであると見なされる。そのようなラムダ関数は、そのクラス型のオブジェクトへの参照を使って、内部のメンバにアクセスできる。

[](SomeType *typePtr) { typePtr->SomePrivateMemberFunction(); };

この例は、このラムダ関数の生成するスコープが SomeType のメンバ関数の中にある場合にのみ動作する。

this ポインタは、各時点でメンバ関数が作動しているオブジェクトを指しているが、その扱いは特別である。ラムダ関数中に明示的な指定が必要になる。

 
[this]() { this->SomePrivateMemberFunction(); };

[&] または [=] 形式のラムダ関数を用いていれば、thisの記述は必要ない。

ラムダ関数の型はコンパイラ依存であり、明示的に記述することはできない。ラムダ関数を引数として取得したい場合は、その型をテンプレート型にするか、std::function などの型消去の仕組みを使う必要がある。auto キーワードを使ってラムダ関数をローカル変数に格納することはできる。

auto myLambdaFunc = [this]() { this->SomePrivateMemberFunction(); };

戻り値を後ろに置く関数構文

[編集]

標準Cの関数宣言の構文は、C言語の機能に対しては最適なものであった。C++ は C から離れて発展してきたが、その基本的な構文を維持しつつ、必要に応じて拡張してきた。しかし、C++ がさらに複雑になってくると、多数の制約、特に関数テンプレートの宣言に関する制約が露呈するようになった。例えば、次のように関数の戻り値の型が文脈によって決まる場合、C++03では明示的にテンプレート型引数で指定するか、boost::result_ofboost::decayのような型推論をエミュレートする補助ライブラリに頼るしかなかった。

template<typename TResult, typename LHS, typename RHS>
TResult AddFunc(const LHS& lhs, const RHS& rhs) { return lhs + rhs; }

int a = AddFunc(1, 2); // コンパイルエラー。
int b = AddFunc<int>(1, 2); // C++03 以前でも、引数の型は実引数から推論できるので省略可能だが、戻り値の型は推論できない。

TResult 型は、LHSRHS の型を加算して作られる型であり、それらの実際の型によって決まる。

C++11では、前述のように decltype という機能が追加されたが、これを直接戻り値の型推論に使うことはできない。

// 正しくない
template<typename LHS, typename RHS>
decltype(lhs+rhs) AddFunc(const LHS& lhs, const RHS& rhs) { return lhs + rhs; }

パーサーが decltype(lhs+rhs) を解析する時点で、lhsrhs はまだ定義されておらず、有効な識別子にはなっていない。したがって、この書き方も C++ に適合していない。

この問題に対処するために、C++11 では次のような関数の定義と宣言の構文が導入された。

template<typename LHS, typename RHS>
auto AddFunc(const LHS& lhs, const RHS& rhs) -> decltype(lhs+rhs) { return lhs + rhs; }

int a = AddFunc(1, 2);

autoでいったん型を前置しておき、後置のdecltypeで補足している。

この構文は、非テンプレート関数の宣言や定義にも使用できる。

struct SomeStruct {
  auto FuncName(int x, int y) -> int;
};

auto SomeStruct::FuncName(int x, int y) -> int {
  return x + y;
}

この構文で使われるautoキーワードは、自動型推論の場合とは異なった意味をもつ。

オブジェクト構築の改良

[編集]

C++03では、コンストラクタは他のコンストラクタを呼び出せず、各コンストラクタでクラスメンバの初期化を全て行わなくてはならない。これはしばしば初期化コードの重複を招く。また、基底クラスのコンストラクタは、派生クラスに直接は公開されない。つまり、基底クラスのコンストラクタと殆ど同等であったとしても、派生クラス側でコンストラクタを定義する必要がある。他にも、constでないデータメンバは、メンバの宣言時に初期化できず、コンストラクタで初期化しなければならない。

C++11では、このような問題点に対する解決策が提供された。

まず、C++11では、コンストラクタが他の同等なコンストラクタを呼び出すこと(委譲)が可能になった。これにより、コンストラクタが他のコンストラクタを最小のコード追加で利用できるようになる。他の言語(Java等)では、既にこれを取り入れているものもある。

構文は以下のようになる:

class SomeType {
  int number;
  
public:
  SomeType(int newNumber) : number(newNumber) {}
  SomeType() : SomeType(42) {}
};

注意すべき点として、C++03では1つのコンストラクタの動作完了と同時にオブジェクトの構築は完了していると考えることができていたが、C++11では一度のオブジェクトの構築に全てのコンストラクタが動作完了しなければならない、と考えなくてはならない点である。複数のコンストラクタが動作することを認めるので、委譲を行う各コンストラクタは完全に構築が完了したオブジェクトに対して動作することを意味する。派生クラスのコンストラクタは、基底クラスでのコンストラクタ間委譲が全て終了した時点で呼び出されることになるであろう。

次に基底クラスのコンストラクタに関して、C++11では、基底クラスのコンストラクタを継承するようにクラスに対して指定することが可能になる。これにより、コンパイラは派生クラスのコンストラクタ呼び出しを基底クラスのそれへと転送するコードを生成することになる。注意すべき点は、この機能は、全てのコンストラクタ呼び出しを基底クラスに転送するか、全く転送しないか、の二者択一の機能であることである。他の注意点として、多重継承時の制限もある。同じシグネチャを持つ2つのコンストラクタに転送することはできないし、転送先のコンストラクタと同じシグネチャを持つコンストラクタを後で宣言することはできない。

構文は以下のようになる:

class BaseClass {
public:
  BaseClass(int value);
  BaseClass(float value);
};

class DerivedClass : public BaseClass {
public:
  using BaseClass::BaseClass;
};

最後にメンバの初期化に関して、C++11では、メンバの初期化に以下のような構文が認められる:

class SomeClass {
  int iValue = 5;
};

この例では、コンストラクタが初期化内容を上書きしない限り、iValueは5に初期化される。上書きする例は次のようになる:

class SomeClass {
  int iValue = 5;
  
public:
  SomeClass() {}
  explicit SomeClass(int iNewValue) : iValue(iNewValue) {}
};

この場合、空のコンストラクタではiValueはクラス定義に従って初期化されるが、int引数を取るコンストラクタの場合はその引数に従って初期化されることになるであろう。

明示的な仮想関数オーバーライド

[編集]

C++03では、ユーザーが基底クラスの仮想関数をオーバーライドする際、誤って意図に反する新しい仮想関数を作成する可能性がある:

struct Base {
  virtual void some_func(float);
  virtual bool on_error();
};

struct Derived : Base {
  virtual void some_func(int);
  virtual bool on_eror();
};

派生クラスDerivedを設計したユーザーは、Base::some_funcおよびBase::on_errorをオーバーライドするつもりで記述したが、それぞれ引数の型や関数名(シグネチャ)が異なるため新しい仮想関数が生成される。上記はC++コードとして誤りではないためコンパイルエラーにならず、間違いに気付くことができない[2]

C++11では、overrideキーワードの導入により、このような問題が解消された:

struct Base {
  virtual void some_func(float);
  virtual bool on_error();
};

struct Derived : Base {
  virtual void some_func(int) override; // 不正:基底クラスの仮想関数をオーバーライドしていない。
  virtual bool on_eror() override; // 不正:同上。
};

overrideキーワードで修飾された仮想関数が、基底クラスのオーバーライド先と一致するかどうかをコンパイラがチェックし、一致しない場合にエラーを報告する。

overrideの他にfinalキーワードがある。finalで修飾された仮想関数のオーバーライドは許可されない。

struct Base {
  virtual void f() const final;
};

struct Derived : Base {
  void f() const; // エラー:Derived::fがfinal Base::fをオーバーライドしようとする。
};

ヌルポインタ

[編集]

C++03では、定数0に、整数定数とヌルポインタという2つの役割が与えられている(この振る舞いは、Cの黎明期(1972年)から続いている)。

長い間プログラマは、0の代わりに定数NULLを使って、この潜在的な曖昧性を大体は回避してきた。しかし、C++になされた2つの設計上の選択が、新たな曖昧性をもたらした。Cでは、NULLはプリプロセッサマクロであり、((void*)0)0と展開されるよう定義されている。C++では、void*型から他のポインタ型への暗黙の変換は認められないので、Cの1つ目の定義と同じくNULLを定義すると、char *c = NULLのような単純な例でもコンパイルエラーになる。これを修正するため、C++ではNULL0へと展開される。0は、あらゆるポインタ型への変換が特別に認められているのである。この結果、オーバーロード機構と酷い相互作用を引き起こす。例えば、以下のような宣言があり

void foo(char *);
void foo(int);

foo(NULL)として呼び出す場合、foo(int)の方が呼ばれることになる。この挙動は、多くの場合に意図されたものではない。

新標準では、ヌルポインタを指定するためにのみ予約された新たなキーワードnullptrが導入される。nullptrは整数型との比較や代入はできないが、あらゆるポインタ型との比較・代入ができる。

互換性のため、0の現行の機能も残されるが、この新構文が上手くいけば、C++委員会は0NULLをヌルポインタとして使用することを非推奨と宣言し、意味の重複を排除するであろう。

強い型付けの列挙型

[編集]

C++03では、列挙型は型安全でなかった。列挙型が異なっていても、実質的には整数として扱われていた。このため、型の違う列挙型の値同士が比較できてしまった。C++03で唯一提供されていた型安全性は、整数や、ある列挙型の値が、暗黙的に他の列挙型には変換されない、ということだけである。また、内部的な整数型はコンパイラの実装に依存しており、列挙型のサイズに依存するコードは可搬ではなかった。さらに、列挙型の値のスコープは、列挙体が定義されたスコープと同じとなる。そのため、二つの列挙型が、同じ名前のメンバをもつことはできなかった。

C++11では、以上のような問題のない、特別なクラス化された列挙型が導入された。enum class(またはenum struct)宣言を用いることで、これを表現できる。

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++11では従来スタイルの列挙型にも、明示的なスコープや内部的な型の指定ができる。

enum Enum3 : unsigned long {Val1 = 1, Val2};

この場合、列挙名は列挙型のスコープで定義される(Enum3::Val1)が、後方互換性のため、列挙型が定義されたスコープにも定義される(Val1)。

C++11では列挙型の前方宣言も可能である。以前は列挙型のサイズが内容によって決まっていたため、列挙型は前方宣言できなかった。明示的あるいは暗黙的に列挙のサイズが決定できさえすれば、前方宣言が可能である。

enum Enum1;                     // C++03とC++11両方で不正。内部的な型が明示されていない。
enum Enum2 : unsigned int;      // C++11では正しい。内部的な型が明示されている。
enum class Enum3;               // C++11では正しい。enum class宣言はデフォルトの型がintである。
enum class Enum4: unsigned int; // C++11では正しい。
enum Enum2 : unsigned short;    // C++11でも不正。Enum2が既に違う型で宣言されている。

山括弧

[編集]

テンプレートを用いた総称プログラミングを導入するのに、新形式の括弧を導入するのが必要であった。そのため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++11の字句解析では、最も深くネストした開き括弧が山括弧"<"である場合、">"は、次に">"や"="が続いていても山閉じ括弧として扱われる。これにより、上記のエラーは最後のものを除いて修正される。最後のものを修正するには、曖昧さを取り除くために丸括弧を足さなくてはならない。

X<(1>2)> x1;  // OK。

こうすることで、"("から")"の間については、コンパイラは<>の文字を山括弧と扱わなくなる。

明示的な変換関数

[編集]

C++規格には、一引数コンストラクタを暗黙的な型変換関数として扱われないようにするための修飾子として、explicitキーワードが導入された。しかし、これは変換関数には何の効果も無い。

例えば、スマートポインタのクラスは、本物のポインタと同じように振る舞うためにoperator bool()を持っていることがある。この変換を追加することで、if (smart_ptr_variable)としてそれをテストできる(ポインタが非NULLならtrueを、NULLならfalseと判定できるはずである)。しかし、この変換を認めると、不本意な変換も発生してしまう。C++のboolは算術型として定義されているため、整数型やさらには浮動小数点型としてまで変換されてしまい、ユーザーが意図しない数値型としての動作を引き起こしてしまうのである。

C++11では、explicitキーワードを変換関数にも適用できるようになる。コンストラクタへの適用と組み合わせ、さらなる暗黙的型変換を防止できる。

別名テンプレート

[編集]

C++03では、typedefを定義する際にできることは、他の型に対して別名を付けることだけであり、存在するすべてのテンプレート引数が指定されたテンプレートの特殊化に対して別名をつけることもこれに含まれる。typedefのテンプレートを作ることはできない。例えば:

template <typename First, typename Second, int Third>
class SomeType;

template <typename Second>
typedef SomeType<OtherType, Second, 5> TypedefName; // C++03では不正

これはコンパイルできない。

C++11では以下の構文でこれが可能になった。

template <typename First, typename Second, int Third>
class SomeType;

template <typename Second>
using TypedefName = SomeType<OtherType, Second, 5>;

C++11ではusing構文は型の別名付けにも使用できる。

typedef void (*Type)(double); // 従来の形式
using OtherType = void (*)(double); // 新たに導入された形式

透過的なガベージコレクション

[編集]

C++11には、透過的ガベージコレクションの機能は直接には導入されない。代わりに、C++11標準には、C++でのガベージコレクションの実装を容易にする仕様が導入された。

完全なガベージコレクションのサポートは、もっと後の標準やTechnical Reportに回されることになった。

制限の無い共用体

[編集]

C++標準にはunionのメンバになれるオブジェクトの型に対する制限がある。例えば、trivialでないコンストラクタが定義されているオブジェクトは共用体の中に含めることが不可能である。共用体に課された制限の多くは無くてもよいと考えられたため、次期標準では参照型を除いて共用体のメンバの型の制限が全廃される。この変更により、共用体は使いやすく強力で有用なものになる。

以下はC++11で許される共用体の簡単な例である:

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++03では不正だが、C++11では問題ない。
};

この変更は現行の規則を緩めるだけなので、既存のコードを破壊することはない。

コア言語機能の改良

[編集]

以下の機能は、従来のC++では全く不可能であったり、異常に冗長な記述が必要だったり、可搬性の無いライブラリを使わないといけなかったりしたような機能を提供するものである。

可変長引数テンプレート

[編集]

C++03のテンプレート(クラステンプレート・関数テンプレート)は、あらかじめ決められた個数の引数しか取れない。C++11では、テンプレート定義の際にあらゆる型の引数を任意個数取れる可変長引数テンプレート (variadic template) をサポートする:

template<typename... Types> class tuple;

このtupleクラステンプレートは、テンプレート引数としていくらでも型名を取れる:

tuple<std::vector<int>, std::map<std::string, std::vector<int>>> someTuple; // tuple インスタンス。
tuple<> emptyTuple; // 引数の数が0の場合。

上記のクラステンプレートはstd::tupleとして標準化されている。

可変長テンプレート引数の個数に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++11における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++03には2種類の文字列リテラルがある。1つ目は、ヌル終端されたconst char型配列を生成する、ダブルクォーテーションで囲まれた形式のものである。2つ目は、ヌル終端されたconst wchar_t型配列(ワイド文字列)を生成する、Lプレフィックスを付けたダブルクォーテーションで囲まれた形式のものである。しかし、どちらもUnicode符号化された文字列リテラルを標準サポートするものではない。

C++コンパイラでのUnicodeサポートを強化するため、char型の定義が修正され、少なくとも8ビット符号単位のUTF-8符号化形式を格納できて、コンパイラの基本実行文字セットのあらゆる文字を格納するのに十分な大きさを持つ、とされた。以前は後者だけを満たしていれば良いとされていた。

C++11では、UTF-8, UTF-16, UTF-32の3つのUnicode符号化形式がサポートされる。先ほど述べたcharの定義の変更に加え、C++11には2つの新たな文字型、char16_tchar32_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.";

1つ目の文字列の型は、普通のconst char *である。2つ目は、const char16_t*であり、3つ目はconst char32_t*となる。

Unicode文字列リテラルを作るときには、Unicodeの符号位置を直接に文字列に埋め込めると便利である。C++11では、以下のような構文が使えるようになる:

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++11では、raw文字列リテラルが導入される:

R"(The String Data \ Stuff " )";
R"delimiter(The String Data \ Stuff " )delimiter";

1つ目の場合、()括弧記号で挟まれた箇所全てが文字列となる。"\の文字はエスケープする必要は無い。2つ目の場合、"delimiter(から)delimiter"までが文字列となる。delimiterという文字列は、実際には任意の文字列でよい。これにより、文字)をraw文字列リテラルで使用できるようになる。

raw文字列リテラルは、ワイドリテラルや各種Unicodeリテラルと組み合わせて利用できる。

ユーザー定義リテラル

[編集]

他の多くの言語と同様、C++03にも数種のリテラル値がある。例えば、"12.5"はコンパイラがdouble型の浮動小数点値へと変換するリテラル値である。しかし、リテラル値にはいくつもの修飾子がある。"12.5f"というリテラルは、浮動小数点値ではあるがfloat型の値を生成するように伝える。このような修飾子はC++仕様として規定されており、C++のコード中では新たな修飾子を生成することは不可能であった。

C++11では、ユーザーが新たな種類のリテラル修飾子を定義できるようにし、リテラル修飾子の文字列を基にオブジェクトを生成できるようになる。

リテラルの変換過程が再定義され、二つの段階、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") と等価

1つ目の文で、接尾辞「_Suffix」を定義している。ユーザーが定義する接尾辞はアンダースコアで始める(アンダースコアで始まらないものは将来の標準のために予約されている)。

2つ目の文では、ユーザー定義リテラル関数によって定義されたコードを実行している。この関数には、Cスタイルの文字列としてヌル終端された"1234"が渡される。

rawリテラルを処理するもう1つの方法は、可変長引数テンプレートを使うことである:

template<char...> OutputType operator""_Suffix();

OutputType someVariable = 1234_Suffix;

これにより、operator""_Suffix<'1', '2', '3', '4'>としてリテラル処理関数が実体化される。この形式では、文字列のヌル終端文字は無い。これを行う主目的は、C++11の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++標準化委員会はマルチスレッドの標準的サポートの導入を計画している。

これには2つの側面がある。つまり、複数スレッドが1つのプログラム中で共存できるメモリモデルを定義することと、スレッド間相互作用のサポートを定義することである。2つ目に関してはライブラリによって提供される。#スレッディングを参照のこと。

複数のスレッドが同じメモリ位置にアクセスする可能性がある環境下でのプログラム記述には、メモリモデルが必要となる。つまり、ルールを遵守するプログラムは正しく実行されるであろうと思われるが、ルールに従わないプログラムはコンパイラの最適化に依存する未定義の振る舞いや、memory coherenceの問題を抱えることになる。

スレッドローカル記憶域

[編集]

マルチスレッド環境においては、各スレッドごとに独立した変数スレッドローカルストレージ、TLS)が必要となることがある。C++03では、関数内のローカル変数(自動変数)はこのような変数に該当するが、グローバル変数、また静的変数としては標準化されていない。各種コンパイラはそれぞれ独自拡張を用意してTLSに対応していた。

C++11では、新たなスレッドローカル記憶クラスが、(既存のstatic動的autoに加えて)新たに追加された。スレッドローカル記憶域は、記憶クラス指定子 thread_localによって指定する。

スレッドローカル記憶クラスは、static記憶クラスを持ち得るあらゆるオブジェクト(プログラムの実行期間全体にわたって存在しているようなもの)にも付けられるだろう。つまり、スレッドローカルなオブジェクトは、static記憶クラスの変数と同様のやり方で、コンストラクタで初期化されデストラクタで破壊されるということである。

コンパイラが生成する関数へのdefault/delete指定

[編集]

C++03では、オブジェクトにコンストラクタ、コピーコンストラクタ、代入演算子 (operator=)、そしてデストラクタが与えられていない場合、コンパイラが自動的にそれらを提供する。ユーザーは自身のバージョンを定義することで、デフォルトの動作を上書きできる。また、C++は全てのクラスに適用されるグローバルな演算子(operator=operator new)も提供しており、その動作もユーザーが上書きできる。

しかし、デフォルトで作られる関数の生成を制御する方法が非常に少ないという問題がある。例えば、クラスを実質的にコピー不能にするには、コピーコンストラクタと代入演算子をprivateとして宣言し、定義しないことによって実現できる。これらの関数を使用しようとすると、コンパイラやリンカがエラーを報告するようになるというわけである。しかし、これは理想的な解法とはいえないであろう。

さらに言えば、例えばデフォルトコンストラクタなど、コンパイラに対して明示的に生成するよう伝えておくことも有用である。オブジェクトに「どんなものであれ一つでも」コンストラクタが定義されているのであれば、コンパイラはデフォルトコンストラクタを生成しない。また、特別なコンストラクタとコンパイラが生成するデフォルトとを両方持っておくことも有用な場面がある。

C++11では、これらコンパイラが生成する関数の使用・不使用を明示できるようになる。例えば、以下のクラスはデフォルトコンストラクタの使用を明示している:

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;
};

doublef()を呼ぼうとすると、暗黙のintへの変換を行うのではなく、コンパイラによってエラーになる。以下のようにすることで、int型以外の型では呼べないようにする総称化ができる:

struct NoDouble {
  void f(int i);
  template<class T> void f(T) = delete;
};

Cとの互換性向上

[編集]

long long int型

[編集]

32ビットおよび64ビット両方のシステムにおいて、サイズが最低でも64ビットあるような整数型が利用できると便利である。C99標準では既にlong long intおよびunsigned long long intとして標準Cに導入されている。また、C++用コンパイラのほとんどで以前から長期的にサポートされている拡張でもある(実際、コンパイラによってはC99で導入される前からサポートしているものもある)。C++11でも同様に、この型が標準C++に導入される。

これは、一部の64ビットシステム上ではあまり有用ではない。例えばLP64モデル上でのデータサイズは以下のようになり、64ビット整数はlong intによって実現できる。

  • 16ビット: short int
  • 32ビット: int
  • 64ビット: long int

それでも、32ビットシステムや、64ビット版Microsoft Windowsに代表されるLLP64モデル(long intが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++標準には、表明を確認する2つの方法として、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-expressionfalseとなる場合、コンパイラはエラーメッセージを生成する。1つ目の例は#errorプリプロセッサディレクティブの代替の例である。2つ目の例の方は、Checkクラステンプレートの各実体化において表明の確認をしている。

静的な表明はテンプレート以外での使用も有益である。例えば、charのビット数 (CHAR_BIT) が8ビット(オクテット)であることや、long longintより大きいサイズであることに依存するアルゴリズムの実装のように、標準では保証されていない箇所の確認などの用途がある(このような仮定はほとんどのシステム、コンパイラで正しいだろうが、全ての環境で正しいとは言えない)。

インスタンス化されていないクラスメンバへのsizeof

[編集]

C++03では、sizeof演算子は型とオブジェクトに対してしか適用できない。そのため、以下のようには使用できない:

struct SomeType { OtherType member; };

size_t memberSize = sizeof(SomeType::member); // C++03では動かない。

この例では、OtherTypeのサイズを取得したい場面であるが、C++03ではこれは不正となりコンパイルエラーとなる。C++11では認められる。

ちなみに、C++03で同様のことをしたい場合、以下のようにすればよい:

struct SomeType { OtherType member; };

size_t memberSize = sizeof(static_cast<SomeType*>(0)->member);

C++標準ライブラリの拡張

[編集]

大量の新機能が、C++11標準ライブラリに追加される。新しいライブラリの多くは現行の標準規格で実装できるが、C++11のコア言語の新機能に(程度の差はあれど)依存しているものもある。

導入されるライブラリの大部分は、C++ Standards Committee's Library Technical Report(TR1と呼ばれる)の文書で定義されている。TR1の最終稿は、2005年に出されている。なお、TR1の多くはBoost C++ライブラリが基になっている。

TR1ライブラリの実装は既に数種類が利用可能であり、std::tr1名前空間を用いて呼び出せる。C++11ではstd名前空間に移動される。

委員会は、2つ目のTechnical Report (C++ Technical Report 2) をC++11の標準化後に予定している。C++11に間に合わないライブラリ提案は、TR2や、さらに先のTechnical Reportに置かれることになる。

以下の提案は、C++11に搭載されることが予定されているものである。

標準ライブラリの改良

[編集]

C++11では言語に多数の新機能が提供され、既存の標準ライブラリもその恩恵を受けられる。例えば、大半の標準ライブラリのコンテナは右辺値参照に基づいたムーブコンストラクタを利用して、重いコンテナを高速に移動したり、新たなメモリ位置に中身を移動できる。標準ライブラリは必要に応じてC++11の新機能を使って改良される。それには下記のようなものが含まれるが、必ずしもこれだけに限定されるわけではない。

  • 右辺値参照とそれに付随するムーブのサポート
  • UTF-16とUTF-32の文字型のサポート
  • 可変個引数テンプレート (完全な転送を可能にするための右辺値参照と共に)
  • コンパイル時の定数式
  • decltype
  • 明示的な変換関数
  • 関数へのdefault/delete指定

加えて、C++が標準化されてから長い時間が経過し、標準ライブラリを使った大量のコードが書かれてきた。その結果、改良を施すことができる標準ライブラリの部分が明らかになった。その場所の多くは標準ライブラリのアロケータである。C++11には現在のアロケータのモデルを補うためにスコープに基づく新しいモデルが含まれる。

スレッディング

[編集]

スレッドクラスにより、関数オブジェクトを元に新たなスレッドの起動が可能になる。各時点において、あるスレッドが実行中のスレッドへのjoin(スレッドの終了を待つこと)も可能である。(可能であれば)ネイティブスレッドオブジェクトにアクセスし、プラットフォーム固有の操作を行うことも可能となるだろう。

スレッド間の同期のためにミューテックス条件変数がライブラリに追加され、RAII方式のロックやロックアルゴリズムの使用も容易になる。

低レベル処理の高効率性のためには、スレッド間通信をミューテックスのオーバーヘッドなしで行う機能も必要となる。これは、適切なメモリバリア不可分操作を用いることで達成できる。不可分操作ライブラリにより、各操作を行うのに必要な最小限の同期だけを指定できる。

スレッドプールのような高レベルスレッディング用のライブラリも作業中であるが、C++11標準にすべて搭載されてはいない。C++11では主に基礎となるライブラリが定義され、将来のTechnical Reportに含まれることが望まれる。

タプル型

[編集]

タプルとは、決められた次元数の、異なる種類の型のオブジェクトから構成されたコレクションである。ペア (std::pair) を拡張して、任意の数のオブジェクトを格納できるようにしたものと考えても良い。あらゆる型のオブジェクトが、タプルの要素になりうる。

この新ユーティリティは、新ヘッダとC++言語拡張を通して実装される。つまり:

  • 可変長引数テンプレート
  • 参照への参照
  • 関数テンプレートのデフォルト引数(C++03ではクラステンプレートにしかデフォルト引数を定義できない)

以下が、<tuple>ヘッダで定義されているタプルである:

template <class... Types> class tuple;

タプル型の定義例と使用例を挙げる:

using test_tuple_t = std::tuple<int, double, long&, const char *>;
long lengthy = 12;
test_tuple_t proof(18, 6.5, lengthy, "Ciao!");
lengthy = std::get<0>(proof); // lengthy に値 18 が代入される。
std::get<3>(proof) = "Beautiful!"; // タプルの第4要素を変更。

タプルproofを内容の定義なしに生成することも可能であるが、タプル要素の型がデフォルトコンストラクタを持つ場合に限られる。さらに、タプルを別のタプルに代入することも可能である。ただし、代入可能なのは、タプルの型が同じで各要素の型がコピーコンストラクタを持つ場合、もしくは右辺側のタプルの各型が左辺側のタプルのそれぞれの型と変換可能な場合、左辺側のタプルの各要素型が適切なコンストラクタを持つ場合である。

std::tuple<int, double, std::string> t1;
std::tuple<char, short, const char *> t2('X', 2, "Hola!");
t1 = t2; // OK。
// 最初の2つの要素は変換可能で、
// 3つ目の型 std::string は const char * から構築できる。

比較演算子も使用可能であり(タプルの要素数が同じ場合)、タプルの特性を確認する2つの式が使用可能である(コンパイル時のみ)。

  • std::tuple_size<T>::value: タプルTの要素数を返す。
  • std::tuple_element<I, T>::type: タプルTI番目のオブジェクトの型を返す。

ハッシュテーブル

[編集]

C++標準ライブラリへのハッシュテーブル(順序付けの無い連想コンテナ)の導入要求は、最も繰り返されてきた要求の1つである。これは、時間的な制約のみのために、現在の標準(1995年1998年)に導入されなかった。ハッシュテーブルは最悪の場合(大量の衝突が発生)の効率は平衡木に劣るが、現実のアプリケーションでは多くの場合効率的である。

衝突の管理は、チェイン法によってのみ行われる。これは、委員会には、多くの実装上の問題(特に要素の消去が認められるような場合)があるオープンアドレス法を標準化する機会がないからである。非標準のライブラリの持つ、独自のハッシュテーブル実装と名前が衝突しないように、"hash"の代わりに"unordered"を使用している。

ハッシュテーブルには4種類あり、同じキーを持つ要素を認めるかどうかと、キーに値を関連付けるかどうかで区別される。

ハッシュ表の型 値の関連付け 重複するキー
unordered_set
unordered_multiset
unordered_map
unordered_multimap

新クラスは、コンテナクラスの要件を全て満たし、要素へのアクセスに必要な全てのメソッドを備える:insert, erase, begin, end.

この新ユーティリティは、C++言語の拡張は必要ない。<functional>ヘッダへの小さな拡張と、<unordered_set>ヘッダ、<unordered_map>ヘッダの導入を行うのみである。他の標準クラスには変更の必要がなく、標準ライブラリの他の拡張にも依存しない。

正規表現

[編集]

正規表現のために作られた、多くの非標準あるいは準標準のライブラリがある。このようなアルゴリズムを使うのは非常に一般的なので、オブジェクト指向言語の能力を用いて、標準ライブラリにもこの機能が導入される。<regex>で提供される正規表現はECMAScriptとの互換性がある。POSIXなどの正規表現もサポートする。ただし、Perlの正規表現はサポートされないため、Perl正規表現を使いたければBoost C++ライブラリのBoost.RegexかBoost.XPressiveを使うことになる。

新ライブラリでは、新ヘッダ<regex>が定義され、2つの新クラスが導入される:

  • 正規表現を表すクラステンプレートbasic_regex
  • 結果を表すクラステンプレートmatch_results

検索のためにregex_search関数が使われ、「検索と置換」とためにregex_replace関数が使われ、新たな文字列を返す。アルゴリズムregex_searchregex_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++11のraw文字列機能により、この問題は回避できる。

regexライブラリ自体は、既存のヘッダの変更や言語拡張を必要としない。

一般用途のスマートポインタ

[編集]

動的メモリ確保は、プログラミング言語の歴史においても昔からの論点である。C/C++では動的に確保したメモリに対する明示的な解放が必要だが、Javaなどに代表される多くの現代的プログラミング言語は、ガベージコレクションによる自動メモリ管理の仕組みを備えている。

C++のポインタには、多くの興味深い性質がある:

  • コピーできる。
  • 代入できる。
  • void *を総称的ポインタとして使える。
  • 静的キャストで、基底クラスのポインタに変換できる。
  • 動的キャストで、派生クラスのポインタに変換できる。

一方、C++のポインタには主に以下のような欠点や危険な性質がある:

  • 動的に割り当てられたメモリを手動管理する責任がプログラマ側にある。
  • メモリ上の、不正なアドレスや未割り当てのアドレスが参照できる。

この問題を解消・軽減するためのイディオムとして、RAIIに基づいたスマートポインタがよく利用される。スマートポインタは、通常のポインタの利点を維持したまま、弱点を大幅に削ったものである。C++03までの標準C++ライブラリにもスマートポインタの一種としてstd::auto_ptrが存在したが、制約が多く、決して使いやすいとは言えなかった。C++11では、Boost C++ライブラリをベースとした、いくつかの新しいスマートポインタが標準化された。

クラステンプレートstd::shared_ptrは、同じオブジェクトに対する「同等の」参照をカウントする機能(参照カウント)を持ち、関連するスマートポインタ間でオブジェクトの所有権を共有する。さらに、標準コンテナの要素とすることも可能である。

同じオブジェクトを参照するポインタの数を得るには、shared_ptrのメンバ関数であるuse_countが使えるだろう。メンバ関数resetは、スマートポインタをリセットする。リセットされたポインタは「空」になり、use_countを呼び出すとゼロを返す。

以下に、shared_ptrの使用例を示す:

#include <iostream>
#include <memory>
int main() {
  std::shared_ptr<double> p_first(new double(-100));
  std::cout << "Ref count = " << p_first.use_count() << std::endl;
  std::cout << *p_first << std::endl;
  {
    std::shared_ptr<double> p_copy = p_first;
    std::cout << "Ref count = " << p_first.use_count() << std::endl;
    std::cout << "Ref count = " << p_copy.use_count() << std::endl;
    std::cout << *p_copy << std::endl;
    *p_copy = 21.2;
  } // 'p_copy'は破壊されるが、まだ参照が残っているので、割り当てられたdoubleは破壊されない。
  std::cout << "Ref count = " << p_first.use_count() << std::endl;
  std::cout << *p_first << std::endl;
  return 0; // 'p_first'が破壊され、参照がなくなり、同時に割り当てられたdoubleも破壊される。
}

これと関連し、弱い参照としてstd::weak_ptrテンプレートも導入される。これもスマートポインタ・参照と同様に振舞うが、shared_ptrとの違いは、参照先の値がポインタによって「所有」されず、弱参照ポインタが存在していても破壊され得る点である。これは、弱参照ポインタには参照先オブジェクトが破壊されてしまうことによる実行時エラーが発生しやすいことを意味している。しかしこれにより、オブジェクトの生存時間に影響しない形で参照を持つことが可能となり、循環参照を防止できる。

このユーティリティは<memory>ヘッダに変更を必要とするが、C++言語拡張は必要としない。

また、std::unique_ptrが前述のauto_ptrの代わりとして導入され、auto_ptrは非推奨とされる。unique_ptrauto_ptrの全機能を提供するが、左辺値からの暗黙的な移動の際に起こる挙動が異なる。unique_ptrは、C++11のMove Semanticsを用いたコンテナになっている。

拡張可能な乱数の枠組み

[編集]

コンピュータは決定的な振る舞いをするものであるが、疑似乱数による乱数列を生成することにより、(外見だけでも)非決定的な振る舞いが必要となるようなアプリケーションがある。

標準に用意されている方法は、rand関数のみである。しかし、十分な定義がされていないため、そのアルゴリズムは完全にライブラリ開発者に任せられている。新たな乱数生成ユーティリティは<random>ヘッダを通して定義される。他のヘッダやC++言語への修正は必要としない。

乱数生成器は、内部状態と、結果を計算し次の状態を生成するための関数をもつ。この2つの特性によって、乱数生成エンジンが定まる。他の特性として、生成結果や乱数の間隔や密度の分布も重要である。

擬似乱数エンジン

[編集]

新ライブラリには、数種の擬似乱数生成エンジンが導入される。これらはテンプレートクラスであり、必要に応じて特殊化できる。擬似乱数エンジンの内部状態は種(一般には変数の集合)によって決定される。

テンプレートクラス int/float 速度 内部状態の大きさ(※)
linear_congruential int 1
subtract_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型の非決定的乱数を生成できる。実装には、システムから独立した入力を持つデバイス(例えば、非同期的な外部カウンタや、特殊なトランスデューサーなど)の使用や、伝統的な「結果を調節する」擬似乱数エンジンの使用が必要である。libstdc++libc++では、/dev/urandomを読み出す実装となっている。

乱数分布

[編集]

新ライブラリには、一様分布から確率論で定義された分布まで、多種類の分布が定義されている:

明らかであるが、標準の分布を実体化しても構わないし、ユーザ自身の互換性のある分布を用いても構わない。

以下は、新ライブラリを用いた簡単な例である:

uniform_int_distribution<int> distribution( 0, 99 );
mt19937 engine;
auto generator( bind( distribution, engine ) );
int random = generator();  // 0から99の値を代入

参照ラッパ

[編集]

参照ラッパはテンプレートクラスstd::reference_wrapperのインスタンスとして得られる。参照ラッパは、C++言語の通常のリファレンス ('&') にも似ている。オブジェクトから参照ラッパを得るには、テンプレート関数std::refを使う(constな参照の場合はstd::crefが使用される)。

参照ラッパはテンプレート関数と使用する場合に有用であり、特に引数としてコピーではなく参照を取る必要がある場合に便利である:

#include <iostream>
#include <functional>

// 引数 r として int への参照を受け取り、それを増加させる関数。
void inc(int& r) {
  r++;
}

// 関数を呼び出す高階関数テンプレート。
template<class F, class P>
void invoke(F f, P t) { f(t); }

int main() {
  int i = 0;
  invoke(inc, i);
  // invoke<void(int& r), int> が実体化される。
  // よって i の値は変更されない。
  std::cout << i << std::endl; // 0 が出力される。

  invoke(inc, std::ref(i));
  // invoke<void(int& r), std::reference_wrapper<int>> が実体化される。
  // よって i の値が変更される。
  std::cout << i << std::endl; // 1 が出力される。

  return 0;
}

この新ユーティリティは現在の<utility>に追加される。C++言語の拡張は必要としない。

なお、上記の高階関数テンプレートは、のちにC++17にて可変長引数テンプレートstd::invokeとして標準化されている[3]

関数オブジェクトの多相的ラッパ

[編集]

関数オブジェクト多相的ラッパ(「多態的関数オブジェクトラッパ」とも呼ばれる)は、意味的にも構文的にも関数ポインタに似ている。しかし、引数がラッパの引数型と変換可能である関数を参照できる点など、束縛が緩和されている。

次の例で、特性を理解できるだろう:

#include <functional>

namespace {
  bool adjacent(long x, long y) {
    return (x + 1 == y) || (x - 1 == y);
  }
}

...

// クラステンプレート std::function を用いてラッパを生成する。
std::function<int (int, int)> funcA;

// 関数ポインタのように、std::function は NULL または nullptr との比較が可能。
std::cout << "Is function empty? " << std::boolalpha << (funcA == nullptr) << std::endl;

// std::function が何も参照していない場合、
// 関数呼び出し演算子を実行すると std::bad_function_call 例外がスローされる。
try {
  const int a = funcA(1, 2);
} catch (const std::exception& ex) {
  std::cout << "Exception reason = " << ex.what() << std::endl;
}

std::plus<int> add;
// std::plus は template<class T> T plus(T, T); として宣言されている。
// そのため、add の型は int add(int x, int y) となる。

funcA = add; // 引数型が一致しているため、代入可能。
std::cout << "Is function empty? " << std::boolalpha << (funcA == nullptr) << std::endl;
std::cout << "ResultA = " << funcA(1, 2) << std::endl;

std::function<bool (short, short)> funcB;
// std::function は explicit operator bool() をサポートする。
if (!funcB) { // 何も代入されていないので、常に成立。
  funcB = &adjacent; // 引数の型・戻り値の型が変換可能なので、代入できる。
  std::cout << "ResultB = " << funcB(0, -1) << std::endl;

  struct test {
    bool operator()(short x, short y) const {
      return (x * y) % 2 == 0;
    }
  } fobj;
  funcB = fobj;
  std::cout << "ResultB = " << funcB(3, 7) << std::endl;
}

funcA = funcB; // ラッパ同士の引数の型・戻り値の型がそれぞれ変換可能なので、正当。

クラステンプレートstd::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_castconst_castはテンプレート中では不十分)。

この種のプログラミングで、美しく簡潔なコードを書ける。しかし、このテクニックの弱点はデバッグである。コンパイル時のデバッグは不適切であるし、プログラム実行時には非常に難しい。

関数の戻り値型を算出する、一様な手法

[編集]

テンプレート関数オブジェクトの戻り値の型を(コンパイル時に)決定するのに、直感的な方法がない。特に引数の型に基づいて決まる場合は顕著である。

例として以下のコードを挙げる:

struct clear {
  int    operator ()(int   ); //引数の型と戻り値の型が同じ
  double operator ()(double);
};

template<class Obj>
class calculus {
  Obj member;
  
public:
  template<class Arg>
  Arg operator ()(Arg& a) const
  {
    return member(a);
  }
};

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 {
  Obj member;
  
public:
  template<class Arg>
  typename result_of<Obj(Arg)>::type operator()(Arg& a) const
  { 
    return member(a);
  }
};

このようにすることで、関数オブジェクトcalculus_ver2<confused>の実体化の際に、型変換は発生しない。

関数オブジェクト呼び出し時の戻り値型の決定は、式の戻り値型を決定するという一般的な問題の一部である。将来的には、問題の起き得る箇所にdecltypeのような機能を使うことで解決されるだろう。

脚注

[編集]
  1. ^ 範囲for文 - cpprefjp C++日本語リファレンス
  2. ^ ただし引数のみが違う場合に関しては、関数オーバーロードによる基底クラスのメンバーの隠蔽となり、通例コンパイラによって警告が出される。
  3. ^ std::invoke - cppreference.com

関連項目

[編集]

参考文献

[編集]

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
  • c Sutter's Mill(August 12, 2011), We have an international standard: C++0x is unanimously approved
  • d ISO - News - C++ language gets high marks on performance with new ISO/IEC standard

外部リンク

[編集]