完全転送の必要性#
以下はクラスのファクトリ関数の例です:
template <typename T, typename Arg>
std::shared_ptr<T> factory(Arg arg) {
return std::shared_ptr<T>( new T(arg));
}
上記の例では、引数オブジェクトarg
は値渡しで渡されていますが、これにより余分な一時オブジェクト1が生成されるため、参照渡しに変更します:
template <typename T, typename Arg>
std::shared_ptr<T> factory(Arg &arg) {
return std::shared_ptr<T>( new T(arg));
}
しかし、この実装では右辺値引数をバインドすることはできません。例えば、factory<X>(42)
はコンパイルエラーになります。さらに、定数参照を使用して次のように変更します:
template <typename T, typename Arg>
std::shared_ptr<T> factory(const Arg &arg) {
return std::shared_ptr<T>( new T(arg));
}
この実装では移動セマンティクスをサポートできないという問題があります。右辺値参照を使用することで、完全転送の問題を解決できます。
参照の折り畳み#
C++11
以前では、参照型をさらに参照することはできませんでしたが、右辺値参照の登場により、この方法が緩和2され、参照の折り畳み規則が生まれました。これにより、参照を参照することができ、左参照と右参照の両方が可能になりました。ただし、次の規則に従います:
関数の形式引数の型 | 実引数の型 | 推論後の関数の形式引数の型 |
---|---|---|
T& | 左参照 | T& |
T& | 右参照 | T& |
T&& | 左参照 | T& |
T&& | 右参照 | T&& |
テンプレートパラメータの型推論#
関数テンプレートtemplate<typename T>void foo(T&&);
に対して、上記の参照の折り畳み規則を適用すると、次の結論が得られます:
- 実引数が型 A の左値の場合、テンプレートパラメータ T の型は
A&
であり、形式引数の型もA&
です。 - 実引数が型 A の右値の場合、テンプレートパラメータ T の型は
A&&
であり、形式引数の型もA&&
です。
これは、クラステンプレートのメンバ関数テンプレートの型推論にも同様に適用されます:
template <class T> class vector {
public:
// Tはクラステンプレートのパラメータなので、このメンバ関数は型推論を必要としません。ここでの関数引数の型はTの右値参照です。
void push_back(T &&x);
// このメンバ関数は関数テンプレートであり、独自のテンプレートパラメータを持つため、型推論が必要です。
template <typename Args> void emplace_back(Args &&args); }
関数テンプレートの形式引数は T&& の形式でなければならず、型推論が必要です。const T&& の形式で宣言された場合でも、文字通りに使用するだけであり、型推論は必要ありません。
完全転送#
以下は上記のコードの完全転送バージョンの実装です:
template <typename T, typename Arg>
shared_ptr<T> factory(Args&& arg) {
return std::shared_ptr<T>( new T(std::forward<Arg>(arg)) );
}
ここで、std::forward
は標準ライブラリ<utility>
に定義されているテンプレート関数です:
template< class T > T&& forward( typename std::remove_reference<T>::type& t ) {
return static_cast<T&&>(t);
}
template< class T > T&& forward( typename std::remove_reference<T>::type&& t ) {
return static_cast<T&&>(t);
}
std::remove_reference
は、標準ライブラリ<type_traits>
に定義されているクラステンプレートであり、型の参照を削除するために使用されます。ここで定義されている type は参照の基本型です。
template< class T > struct remove_reference {typedef T type;};
template< class T > struct remove_reference<T&> {typedef T type;};
template< class T > struct remove_reference<T&&> {typedef T type;};
- 実引数のデータ型が左値参照型
S&
の場合 (T = S&
)、t
の型はS&
であり、static_cast<S& &&>(t)
はstatic_cast<S&>(t)
に折り畳まれます。 - 実引数のデータ型が右値参照型
S&&
の場合 (T = S&&
)、t
の型はS&&
であり、static_cast<S&& &&>(t)
はstatic_cast<S&&>(t)
に折り畳まれます。
参考文献#
https://zh.wikipedia.org/zh-cn/%E5%8F%B3%E5%80%BC%E5%BC%95%E7%94%A8