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之前,我們不能對一個引用類型繼續引用,但C++由於右值引用的出現而放寬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)

References#

https://zh.wikipedia.org/zh-cn/%E5%8F%B3%E5%80%BC%E5%BC%95%E7%94%A8

Footnotes#

  1. 臨時物件的生命週期只有一條語句的時間。

  2. 這裡使用放寬一詞是因為:只有在類型別名和模板參數時可以間接定義引用的引用。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。