0xe3aad

0xe3aad

C++ パーフェクトフォワーディング

完全転送の必要性#

以下はクラスのファクトリ関数の例です:

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

Footnotes#

  1. 一時オブジェクトのライフサイクルは 1 つの文の時間だけです。

  2. ここで「緩和」という言葉を使用するのは、参照の参照を間接的に定義できるのは、型エイリアスとテンプレートパラメータの場合だけだからです。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。