<boost/call_traits.hpp>
的所有內容都定義在 namespace boost
內。
樣板類別 call_traits<T>
封裝了將某個類型 T
的參數傳遞到函式或從函式傳遞出來的「最佳」方法,並包含一組在下表中定義的 typedef
。 call_traits
的目的是確保諸如 「參考的參考」 之類的問題永遠不會發生,並且參數以盡可能最有效率的方式傳遞,如 範例 所示。在每種情況下,如果您的現有做法是使用左側定義的類型,則請將其替換為右側定義的 call_traits
類型。
請注意,對於不支援部分特化或成員樣板的編譯器,使用 call_traits
不會產生任何好處:在這種情況下,call_traits
定義的類型將始終與現有做法相同。此外,如果編譯器僅支援成員樣板而不支援部分樣板特化(例如 Visual C++ 6),則 call_traits
無法與陣列類型一起使用,儘管它仍然可以用來解決參考的參考問題。
表 1.2. call_traits
類型
現有做法 |
|
描述 |
備註 |
---|---|---|---|
(傳回值) |
|
定義一個表示類型 將其用於按值傳回的函式,或可能用於類型 |
2 |
(傳回值) |
|
定義一個表示類型 用於通常會傳回 |
1 |
(傳回值) |
|
定義一個表示類型 用於通常會傳回 |
1 |
(函式參數) |
|
定義一個表示將類型 |
1,3 |
備註
T
已經是參考類型,則定義 call_traits
以使 「參考的參考」 不會發生(需要部分特化)。T
是陣列類型,則 call_traits
將 value_type
定義為「指向類型的常數指標」,而不是「類型陣列」(需要部分特化)。請注意,如果您將 value_type
用作儲存值,則會導致儲存「指向陣列的常數指標」,而不是陣列本身。這可能是好事,也可能不是好事,具體取決於您的實際需求(換句話說,請小心!)。T
是小的內建類型或指標,則 param_type
定義為 T const
,而不是 T const&
。如果函式主體中的迴圈取決於傳遞的參數,這可以提高編譯器最佳化迴圈的能力,否則傳遞參數的語意不會改變(需要部分特化)。下表定義了哪些 call_traits
類型始終可以從哪些其他類型複製建構
表 1.3. 哪些 call_traits
類型始終可以從哪些其他類型複製建構
到 |
到 |
到 |
到 |
到 |
|
---|---|---|---|---|---|
從 |
若且唯若 |
若且唯若 |
是 |
是 |
是 |
從 |
若且唯若 |
若且唯若 |
否 |
否 |
是 |
從 |
若且唯若 |
若且唯若 |
是 |
是 |
是 |
從 |
若且唯若 |
否 |
否 |
是 |
是 |
從 |
若且唯若 |
若且唯若 |
否 |
否 |
是 |
如果 T
是可賦值的類型,則可以進行以下賦值
表 1.4. 哪些 call_traits
類型可以從哪些其他類型賦值
到 |
到 |
到 |
到 |
到 |
|
---|---|---|---|---|---|
從 |
是 |
是 |
- |
- |
- |
從 |
是 |
是 |
- |
- |
- |
從 |
是 |
是 |
- |
- |
- |
從 |
是 |
是 |
- |
- |
- |
從 |
是 |
是 |
- |
- |
- |
下表顯示了 call_traits
對各種類型的影響。
表 1.5. call_traits
類型的範例
|
|
|
|
適用於 |
|
---|---|---|---|---|---|
從 |
|
|
|
|
所有使用者定義的類型 |
從 |
|
|
|
|
所有小的內建類型 |
從 |
|
|
|
|
所有指標類型 |
從 |
|
|
|
|
所有參考類型 |
從 |
|
|
|
|
所有常數參考類型 |
從 |
|
|
|
|
所有陣列類型 |
從 |
|
|
|
|
所有常數陣列類型 |
此表假設編譯器支援部分特化:如果不支持,則所有類型的行為方式都與 "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 typenameboost::call_traits
<T>::param_type param_type; typedef typenameboost::call_traits
<T>::reference reference; typedef typenameboost::call_traits
<T>::const_reference const_reference; typedef T value_type; typedef typenameboost::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
< typenameboost::call_traits
<T1>::value_type, typenameboost::call_traits
<T2>::value_type> make_pair(const T1& t1, const T2& t2) { returnstd::pair
< typenameboost::call_traits
<T1>::value_type, typenameboost::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, typenameboost::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_type
和 call_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(typenamecall_traits
<T>::value_type t); }; template <class T> void A<T>::foo(typenamecall_traits
<T>::value_type t) { typenamecall_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]>; }