右辺値参照(C++11)
BCC32 では右辺値参照の使用をサポートしており、一時値の参照を作成できます。 また、右辺値参照により、不必要なコピーが回避され、完全転送関数が可能になります。 この機能は C++11 機能の 1 つです。
説明
右辺値参照は標準 C++ 参照(左辺値参照と呼ばれる)と同様の複合型です。左辺値参照を作るには、アンパサンド文字(&)を型に付加します。
SomeClass l; SomeClass& lReference = l; //lvalue reference
右辺値参照の構文は、型の後に && を付け加えます。
SomeClass r; SomeClass&& rReference = r; //rvalue reference
右辺値参照は左辺値参照と同様に動作します。ただし、右辺値参照は一時値(右辺値)にバインドすることができます。
SomeClass a; a = SomeClass(); SomeClass& lReference = a; //OK-lvalue reference can bind to an lvalue such as "a" SomeClass& lReference2 = SomeClass(); //error-lvalue reference can't bind to an rvalue SomeClass&& rReference = SomeClass(); //OK for rvalue reference to bind to rvalue // Both references can be used the same way SomeOtherClass value1 = SomeFunc(lReference); SomeOtherClass value2 = SomeFunc(rReference);
上記の例では、SomeClass() は識別子にはバインドされていません。したがって右辺値であり、右辺値参照にバインドできます。しかし、左辺値参照にはバインドできません。
右辺値参照は名前付き左辺値にバインド不能
C++ 仕様により、新しい右辺値参照規則が設定されました。右辺値参照のバインディング規則は、1 つの面で働きが変わりました。手短に言えば、右辺値参照は名前付き左辺値にはバインドできなくなりました(オーバーロード解決の場合を除きます。こちらの方は変更ありません)。
Dinkumware ヘッダーはまだ古い規則に従っているため、Dinkumware ヘッダーの右辺値参照機能を使用する場合は、新しいオプション -Vbv を付けてコンパイルする必要があります。
不必要なコピーの解消
多くの場合、ただ移動が必要な場合、つまり、元のデータ ホルダでデータを保持する必要がない場合でも、データがコピーされます。たとえば、2 つの構造体のデータを交換する場合は、どちらの構造体も前のデータを保持しません。データの参照を切り替えるだけで論理的には十分です。
右辺値参照は、コピーが必要な場合とただデータの移動が必要な場合を区別するために使用できます。コピーは長時間に及ぶ可能性のある操作であるため、できれば避けたいところです。
右辺値として渡されたものを関数がコピーする場合は、コピーではなく移動を行えます。値が一時的であることがわかっているからです。関数に左辺値が渡される場合は、コピーを省略できなければ、完全コピーを行わなければならない可能性があります。これらの場合を関数シグネチャで区別できます。
クラス インスタンスの "深いコピー" を行うクローン関数があるクラス ExampleClass について考えてみましょう。オブジェクトの値を移動する move 関数を定義します。この関数は次のようにオーバーロードできます。
// Parameter is lvalue ExampleClass move(ExampleClass& l) { return l.clone(); //returns a copy since we can't touch the original value // Parameter is rvalue ExampleClass move(ExampleClass&& r) { return r; //returns a reference since we don't care about the temporary's value }
template class MyClass<int>; ... extern template class MyClass<int>; // not allowed extern template class MyClass<float>; // OK
右辺値にも左辺値にも move 関数を使用できます。
ExampleClass a, b, c; a = ExampleClass(); b = b.move(a); //parameter is lvalue c = c.move(ExampleClass()); //parameter is rvalue
なお、右辺値パラメータの場合 move 関数はほとんど行うことがないので、左辺値パラメータの場合の移動よりはるかに高速に実行されます。
コピーを行う必要がある関数(コピー コンストラクタや代入演算子など)に同様の手法を用いることができます。何か他のクラスへのポインタを持つテンプレート クラスがあるとしましょう。この場合も clone は深いコピーを行う関数です。
template <class T> class PointerClass { private: T* pointer; public: // Regular constructor PointerClass(void); // Copy constructor for lvalues PointerClass(PointerClass& pcl) : pointer(pcl.pointer ? pcl.pointer.clone() : 0) {} //make full copy // Copy constructor for rvalues 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); // compiler error 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; }
関連項目
- C++11 対応機能:インデックス
- A Proposal to Add an Rvalue Reference to the C++ Language(C++ 言語への右辺値参照を追加する提案)
- A Brief Introduction to Rvalue References(右辺値参照の概略)
- 左辺値参照(C++)