右辺値参照(C++0x)

提供: Appmethod Topics
移動先: 案内検索

C++0x 対応機能:インデックス への移動


BCC32 では右辺値参照の使用をサポートしており、一時値の参照を作成できます。また、右辺値参照により、不必要なコピーが回避され、完全転送関数が可能になります。この機能は C++0x 機能の 1 つです。

説明

右辺値参照は標準 C++ 参照(左辺値参照と呼ばれる)と同様の複合型です。左辺値参照を作るには、アンパサンド文字(&)を型に付加します。

SomeClass l;
SomeClass& lReference = l;    // 左辺値参照

右辺値参照の構文は、型の後に && を付け加えます。

SomeClass r;
SomeClass&& rReference = r;    // 右辺値参照

右辺値参照は左辺値参照と同様に動作します。ただし、右辺値参照は一時値(右辺値)にバインドすることができます。

SomeClass a;
a = SomeClass();
SomeClass& lReference = a;	        // OK - 左辺値参照は "a" などの左辺値にバインドできる
SomeClass& lReference2 = SomeClass();	//エラー - 左辺値参照は右辺値にバインドできない
SomeClass&& rReference = SomeClass();	// OK - 右辺値参照は右辺値にバインドできる

// どちらの参照も同じように使用できる
SomeOtherClass value1 = SomeFunc(lReference);
SomeOtherClass value2 = SomeFunc(rReference);

上記の例では、SomeClass() は識別子にはバインドされていません。したがって右辺値であり、右辺値参照にバインドできます。しかし、左辺値参照にはバインドできません。

右辺値参照は名前付き左辺値にバインド不能

C++ 仕様により、新しい右辺値参照規則が設定されました。右辺値参照のバインディング規則は、1 つの面で働きが変わりました。手短に言えば、右辺値参照は名前付き左辺値にはバインドできなくなりました(オーバーロード解決の場合を除きます。こちらの方は変更ありません)。

Dinkumware ヘッダーはまだ古い規則に従っているため、Dinkumware ヘッダーの右辺値参照機能を使用する場合は、新しいオプション -Vbv を付けてコンパイルする必要があります。

不必要なコピーの解消

多くの場合、ただ移動が必要な場合、つまり、元のデータ ホルダでデータを保持する必要がない場合でも、データがコピーされます。たとえば、2 つの構造体のデータを交換する場合は、どちらの構造体も前のデータを保持しません。データの参照を切り替えるだけで論理的には十分です。

右辺値参照は、コピーが必要な場合とただデータの移動が必要な場合を区別するために使用できます。コピーは長時間に及ぶ可能性のある操作であるため、できれば避けたいところです。

右辺値として渡されたものを関数がコピーする場合は、コピーではなく移動を行えます。値が一時的であることがわかっているからです。関数に左辺値が渡される場合は、コピーを省略できなければ、完全コピーを行わなければならない可能性があります。これらの場合を関数シグネチャで区別できます。

クラス インスタンスの "深いコピー" を行うクローン関数があるクラス ExampleClass について考えてみましょう。オブジェクトの値を移動する move 関数を定義します。この関数は次のようにオーバーロードできます。

// パラメータが左辺値
ExampleClass move(ExampleClass& l)
{
    return l.clone();	// 元の値を操作できないのでコピーを返す

// パラメータが右辺値
ExampleClass move(ExampleClass&& r)
{
    return r;		// 一時値には関心がないので参照を返す
}
template class MyClass<int>;
...
extern template class MyClass<int>;    // 不可
extern template class MyClass<float>;  // OK

右辺値にも左辺値にも move 関数を使用できます。

ExampleClass a, b, c;
a = ExampleClass();
b = b.move(a);			// パラメータは左辺値
c = c.move(ExampleClass());	// パラメータは右辺値

なお、右辺値パラメータの場合 move 関数はほとんど行うことがないので、左辺値パラメータの場合の移動よりはるかに高速に実行されます。

コピーを行う必要がある関数(コピー コンストラクタや代入演算子など)に同様の手法を用いることができます。何か他のクラスへのポインタを持つテンプレート クラスがあるとしましょう。この場合も clone は深いコピーを行う関数です。

template <class T>
class PointerClass
{
private:
    T* pointer;
public:
    // 通常のコンストラクタ
    PointerClass(void);

    // 左辺値の場合のコピー コンストラクタ
    PointerClass(PointerClass& pcl) : pointer(pcl.pointer ? pcl.pointer.clone() : 0) {}	// 完全コピーを行う
    // 右辺値の場合のコピー コンストラクタ
    PointerClass(PointerClass&& pcr) : pointer(pcr.pointer) {pcr.pointer = 0;}

右辺値を受け取るコピー コンストラクタは、

  • コピーではなく、移動を行います。つまり、データの参照を返すだけです。
  • コードでは右辺値引数 pcr を左辺値と同じように扱います。
  • 安全に削除できるように、右辺値オブジェクトを定義された状態のままにしておきます。

コピー不可で移動可能な型

unique_ptr を使用するような、コピー可能ではない型は移動可能にできます。代入演算子を定義できませんが、移動関数とスワップ関数を実装できます。右辺値参照を使用するときコピーが必要ないからです。コピーではなく、スワップのみが必要であるから、ソート関数を開発できます。

たとえば、1 つの引数をとるファクトリ関数を考えます。

template <class T, class U>
factory(const U& u)
{
    return new T(u);
}

このファクトリの定義は次の場合に動作します。

T* p = factory<T>(7);

ただし、コンストラクタのパラメータが非定数参照である T が使用されているときにコンパイル エラーが発生します。定義から定数を削除してこの問題を修正できます。

template <class T, class U>
factory(U& u)
{
    return new T(u);
}

ただし、前の例でもエラーが発生します。

T* p = factory<T>(7); // コンパイラ エラー
T* u = new T(7);	// OK

値 7 によりテンプレート引数が int & に一致しますが、右辺値 7 にバインドされないからエラーが発生します。

定数と非定数それぞれにファクトリ関数を定義することによってこれを解決できます。ただし、これには問題があります。必要な関数の数が引数の数により指数的に増大するためです。

引数を右辺値参照にする場合は、状況を簡単にできます。

template <class T, class U>
factory(u&& u)
{
   return new T(forward<U>(u));
}

ここで引数 u は右辺値と左辺値の両方にバインドされます。forward 関数は渡されたとおり正確に右辺値または左辺値を返します。次のように定義できます。

template <class U>
struct identity
{
    typedef U type;
};

template <class U>
U&& forward(typename identity<U>::type&& u)
{
    return u;
}

関連項目