Boost C++ 函式庫

...世界上最受推崇且設計最精良的 C++ 函式庫專案之一。 Herb SutterAndrei Alexandrescu, C++ 編碼標準

PrevUpHomeNext

呼叫特性

簡介
複製建構性
範例
基本原理
參考資料

<boost/call_traits.hpp> 的所有內容都定義在 namespace boost 內。

樣板類別 call_traits<T> 封裝了將某個類型 T 的參數傳遞到函式或從函式傳遞出來的「最佳」方法,並包含一組在下表中定義的 typedefcall_traits 的目的是確保諸如 「參考的參考」 之類的問題永遠不會發生,並且參數以盡可能最有效率的方式傳遞,如 範例 所示。在每種情況下,如果您的現有做法是使用左側定義的類型,則請將其替換為右側定義的 call_traits 類型。

請注意,對於不支援部分特化或成員樣板的編譯器,使用 call_traits 不會產生任何好處:在這種情況下,call_traits 定義的類型將始終與現有做法相同。此外,如果編譯器僅支援成員樣板而不支援部分樣板特化(例如 Visual C++ 6),則 call_traits 無法與陣列類型一起使用,儘管它仍然可以用來解決參考的參考問題。

表 1.2. call_traits 類型

現有做法

call_traits 等效項目

描述

備註

T

(傳回值)

call_traits<T>::value_type

定義一個表示類型 T 的「值」的類型。

將其用於按值傳回的函式,或可能用於類型 T 的儲存值。

2

T&

(傳回值)

call_traits<T>::reference

定義一個表示類型 T 的參考的類型。

用於通常會傳回 T& 的函式。

1

const T&

(傳回值)

call_traits<T>::const_reference

定義一個表示類型 T 的常數參考的類型。

用於通常會傳回 const T& 的函式。

1

const T&

(函式參數)

call_traits<T>::param_type

定義一個表示將類型 T 的參數傳遞給函式的「最佳」方式的類型。

1,3


備註

  1. 如果 T 已經是參考類型,則定義 call_traits 以使 「參考的參考」 不會發生(需要部分特化)。
  2. 如果 T 是陣列類型,則 call_traitsvalue_type 定義為「指向類型的常數指標」,而不是「類型陣列」(需要部分特化)。請注意,如果您將 value_type 用作儲存值,則會導致儲存「指向陣列的常數指標」,而不是陣列本身。這可能是好事,也可能不是好事,具體取決於您的實際需求(換句話說,請小心!)。
  3. 如果 T 是小的內建類型或指標,則 param_type 定義為 T const,而不是 T const&。如果函式主體中的迴圈取決於傳遞的參數,這可以提高編譯器最佳化迴圈的能力,否則傳遞參數的語意不會改變(需要部分特化)。

下表定義了哪些 call_traits 類型始終可以從哪些其他類型複製建構

表 1.3. 哪些 call_traits 類型始終可以從哪些其他類型複製建構

T

value_type

reference

const_reference

param_type

T

若且唯若 T 可複製建構

若且唯若 T 可複製建構

value_type

若且唯若 T 可複製建構

若且唯若 T 可複製建構

reference

若且唯若 T 可複製建構

若且唯若 T 可複製建構

const_reference

若且唯若 T 可複製建構

param_type

若且唯若 T 可複製建構

若且唯若 T 可複製建構


如果 T 是可賦值的類型,則可以進行以下賦值

表 1.4. 哪些 call_traits 類型可以從哪些其他類型賦值

T

value_type

reference

const_reference

param_type

T

-

-

-

value_type

-

-

-

reference

-

-

-

const_reference

-

-

-

param_type

-

-

-


下表顯示了 call_traits 對各種類型的影響。

表 1.5. call_traits 類型的範例

call_traits::value_type

call_traits::reference

call_traits::const_reference

call_traits::param_type

適用於

my_class

my_class

my_class&

const my_class&

my_class const&

所有使用者定義的類型

int

int

int&

const int&

int const

所有小的內建類型

int*

int*

int*&

int* const &

int* const

所有指標類型

int&

int&

int&

const int&

int&

所有參考類型

const int&

const int&

const int&

const int&

const int&

所有常數參考類型

int[3]

const int*

int(&)[3]

const int(&)[3]

const int* const

所有陣列類型

const int[3]

const int*

const int(&)[3]

const int(&)[3]

const int* const

所有常數陣列類型


此表假設編譯器支援部分特化:如果不支持,則所有類型的行為方式都與 "my_class" 的條目相同,並且 call_traits 無法與參考或陣列類型一起使用。

以下的類別是一個簡單的類別,它透過值來儲存某種類型 T (請參閱 call_traits_test.cpp 檔案)。目的是要說明如何使用每個可用的 call_traits typedef

template <class T>
struct contained
{
   // define our typedefs first, arrays are stored by value
   // so value_type is not the same as result_type:
   typedef typename boost::call_traits<T>::param_type       param_type;
   typedef typename boost::call_traits<T>::reference        reference;
   typedef typename boost::call_traits<T>::const_reference  const_reference;
   typedef T                                                value_type;
   typedef typename boost::call_traits<T>::value_type       result_type;

   // stored value:
   value_type v_;

   // constructors:
   contained() {}
   contained(param_type p) : v_(p){}
   // return byval:
   result_type value() { return v_; }
   // return by_ref:
   reference get() { return v_; }
   const_reference const_get()const { return v_; }
   // pass value:
   void call(param_type p){}

};

考慮 std::binder1st 的定義

template <class Operation>
class binder1st :
   public std::unary_function<typename Operation::second_argument_type, typename Operation::result_type>
{
protected:
   Operation op;
   typename Operation::first_argument_type value;
public:
   binder1st(const Operation& x, const typename Operation::first_argument_type& y);
   typename Operation::result_type operator()(const typename Operation::second_argument_type& x) const;
};

現在考慮以下相對常見的情況:函式物件將其第二個參數作為參考,這表示 Operation::second_argument_type 是一個參考類型,operator() 現在將會以「參考到參考」作為參數,而這目前是不合法的。這裡的解決方案是修改 operator() 以使用 call_traits

typename Operation::result_type operator()(typename call_traits<typename Operation::second_argument_type>::param_type x) const;

現在,在 Operation::second_argument_type 是參考類型的情況下,參數會以參考方式傳遞,並且不會發生「參考到參考」的情況。

如果我們將陣列的名稱作為 std::make_pair 的一個 (或兩個) 參數傳遞,則範本引數推導會將傳遞的參數推導為「T 的陣列的 const 參考」,這也適用於字串文字 (實際上是陣列文字)。因此,它不是傳回一對指標,而是嘗試傳回一對陣列,並且由於陣列類型不可複製建構,因此程式碼無法編譯。一種解決方案是將 std::make_pair 的引數明確地轉換為指標,但 call_traits 提供了一個更好的自動解決方案,即使在泛型程式碼中 (其中轉換可能會做錯事) 也能安全地運作。

template <class T1, class T2>
std::pair<
   typename boost::call_traits<T1>::value_type,
   typename boost::call_traits<T2>::value_type>
      make_pair(const T1& t1, const T2& t2)
{
   return std::pair<
      typename boost::call_traits<T1>::value_type,
      typename boost::call_traits<T2>::value_type>(t1, t2);
}

在這裡,如果推導的類型是陣列,則推導的引數類型將自動降級為指標,標準的繫結器和配接器中也會發生類似的情況:原則上,在任何「包裝」推導出類型的暫時物件的函式中。請注意,std::make_pair 的函式引數並非以 call_traits 表示:這樣做會阻止範本引數推導運作。

call_traits 範本將「最佳化」以函式參數形式傳遞的小型內建類型。這主要在參數在迴圈主體內使用時才會產生影響。

在以下範例中 (請參閱 fill_example.cpp),std::fill 的一個版本透過兩種方式進行最佳化:如果傳遞的類型是單一位元組內建類型,則使用 std::memset 來執行填入;否則,會使用傳統的 C++ 實作,但是會使用 call_traits「最佳化」傳遞的參數。

template <bool opt>
struct filler
{
   template <typename I, typename T>
   static void do_fill(I first, I last, typename boost::call_traits<T>::param_type val)
   {
      while(first != last)
      {
         *first = val;
         ++first;
      }
   }
};

template <>
struct filler<true>
{
   template <typename I, typename T>
   static void do_fill(I first, I last, T val)
   {
      std::memset(first, val, last-first);
   }
};

template <class I, class T>
inline void fill(I first, I last, const T& val)
{
   enum { can_opt = boost::is_pointer<I>::value
                   && boost::is_arithmetic<T>::value
                   && (sizeof(T) == 1) };
   typedef filler<can_opt> filler_t;
   filler_t::template do_fill<I,T>(first, last, val);
}

對於小型內建類型而言,之所以「最佳化」的原因是,以 T const 而非 const T& 形式傳遞值時,編譯器可以判斷該值是常數且沒有別名。透過此資訊,編譯器能夠將傳遞的值快取在暫存器中、展開迴圈,或使用明確的平行指令:如果支援任何這些指令。從這裡獲得多少效能取決於您的編譯器 - 我們真的很需要一些精確的效能評測軟體來作為 boost 的一部分,以應對這種情況。

請注意,填入的函式引數並非以 call_traits 表示:這樣做會阻止範本引數推導運作。相反地,填入充當一個「薄包裝函式」,旨在執行範本引數推導,編譯器會完全最佳化掉對填入的呼叫,並將其替換為對 filler<>::do_fill 的呼叫,而該呼叫會使用 call_traits

以下註解旨在簡要描述 call_traits 中所做選擇的基本原理。

所有使用者定義的類型都遵循「現有慣例」,因此無需註解。

小型內建類型 (標準中稱為 基本類型) 與現有慣例的不同之處僅在於 param_type typedef。在這種情況下,傳遞 T const 與現有慣例相容,但在某些情況下可以提高效能 (請參閱範例 4)。無論如何,這絕不應該比現有慣例更糟。

指標遵循與小型內建類型相同的基本原理。

對於參考類型,基本原理遵循範例 2 - 不允許使用參考到參考,因此必須定義 call_traits 成員,以避免發生這些問題。有一項提案要修改語言,使「參考到參考就是參考」(Bjarne Stroustrup 提出的問題 #106)。call_traits<T>::value_typecall_traits<T>::param_type 都提供了與該提案相同的效果,而無需變更語言。換句話說,這是一種解決方法。

對於陣列類型,將陣列作為引數的函式會將陣列類型降級為指標類型:這表示實際參數的類型與其宣告的類型不同,這可能會在依賴參數宣告類型的範本程式碼中造成無休止的問題。

例如

template <class T>
struct A
{
   void foo(T t);
};

在這種情況下,如果我們實例化 A<int[2]>,則傳遞給成員函式 foo 的參數宣告類型為 int[2],但其實際類型為 const int*。如果我們嘗試在函式主體內使用類型 T,則我們的程式碼很有可能無法編譯

template <class T>
void A<T>::foo(T t)
{
   T dup(t); // doesn't compile for case that T is an array.
}

透過使用 call_traits,從陣列到指標的降級是明確的,並且參數的類型與其宣告的類型相同

template <class T>
struct A
{
   void foo(typename call_traits<T>::value_type t);
};

template <class T>
void A<T>::foo(typename call_traits<T>::value_type t)
{
   typename call_traits<T>::value_type dup(t); // OK even if T is an array type.
}

對於 value_type (依值傳回),也只能傳回指標,而不能傳回整個陣列的副本,而且 call_traits 會明確說明降級。value_type 成員在必須將陣列明確降級為指標時非常有用 - 範例 3 提供了測試案例。

附註:call_traits 的陣列特化是所有 call_traits 特化中最不被理解的。如果給定的語意對您造成特定問題,或無法解決特定的陣列相關問題,我很樂意聽取您的意見。但是,大多數人可能永遠不需要使用此特化。

參考資料

namespace boost {
  template<typename T> struct call_traits;

  template<typename T, std::size_t N> struct call_traits<const T[N]>;
  template<typename T> struct call_traits<T &>;
  template<typename T, std::size_t N> struct call_traits<T[N]>;
}

PrevUpHomeNext