頭文件 <boost/call_traits.hpp>

<boost/call_traits.hpp> 的所有內容定義於名字空間 boost 中。

模板類 call_traits<T> 封裝了 "最好的" 方法來傳遞某種類型 T 的參數給函數,或從函數返回,它由下表所列的一組 typedef 定義所組成。call_traits 的目的是,確保不會發生象 "引用的引用" 這樣的情況,而且參數以最高效的方式傳遞(請見 例子)。如果你現在是使用左邊所定義的類型,那麼請將它替換為右邊所定義的 call_traits 類型。

注意,對於不支持偏特化或成員模板的編譯器,不能從使用 call_traits 中得到好處:這種情形下 call_traits 定義的類型總是與現有類型保持一致。另外如果編譯器只支持成員模板而不支持偏特化(如 Visual C++ 6),那麼 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_traits 定義 value_type 為 "類型的常量指針" 而不是 "類型的數組" (需要偏特化)。注意,如果你將 value_type 用作一個保存值,則會導致保存了一個 "指向數組的常量指針" 而不是數組本身。這可能是也可能不是一個好事,取決於你的真正需要(換句話說,就是要當心!)。
  3. 如果 T 是一個小的內建類型或指針,則 param_type 定義為 T const, 而不是 T const&. 這可以提升編譯器對函數體中的循環的優化,如果它們依賴於傳入的參數,傳入參數的語義並沒有改變(需要偏特化)。

 

可複製構造

下表定義了哪些 call_traits 類型總是可以從其它類型進行複製構造,標記了 '?' 的項為 true 當且僅當 T 是可複製構造的:

 

到:

從:

T

value_type

reference

const_reference

param_type

T

?

?

Y

Y

Y

value_type

?

?

N

N

Y

reference

?

?

Y

Y

Y

const_reference

?

N

N

Y

Y

param_type

?

?

N

N

Y

 

如果 T 是一個可賦值類型,則以下賦值是可以的:

 

到:

從:

T

value_type

reference

const_reference

param_type

T

Y

Y

-

-

-

value_type

Y

Y

-

-

-

reference

Y

Y

-

-

-

const_reference

Y

Y

-

-

-

param_type

Y

Y

-

-

-

 

例子

下表列出了 call_traits 對不同類型的作用,這裡假定編譯器支持偏特化:如果不支持的話,那麼所有類型的行為與 "myclass" 那行一樣,且 call_traits 不可用於引用或數組類型。

 

Call_traits 的類型:

原先的類型 T

value_type

reference

const_reference

param_type

應用於:

myclass

myclass

myclass&

const myclass&

myclass 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

所有常量數組類型

 

例 1:

以下這個類是一個簡單的類,以值方式保存類型 T (見 call_traits_test.cpp 文件),其目的是示範如何使用每個可用的 call_traits:

template <class T>
struct contained
{
// 首先定義我們的 typedefs, 數組是以值方式存儲的
// 所以 value_type 與 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;

// 被保存的值:
value_type v_;

// 構造函數:
contained() {}
contained(param_type p) : v_(p){}
// 以值返回:
result_type value() { return v_; }
// 以引用返回:
reference get() { return v_; }
const_reference const_get()const { return v_; }
// 以值傳遞:
void call(param_type p){}

};

例 2 ("引用的引用"的問題):

考慮 std::binder1st 的定義:

template <class Operation> 
class binder1st :
public 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 是一個引用類型,也會以引用方式來傳遞參數,而不會發生 "引用的引用"。

例 3 (make_pair 問題):

如果我們將一個數組的名字作為一個(或兩個)參數傳遞給 std::make_pair, 那麼模板參數推斷將認為傳遞的參數是 "T 數組的常量引用",這種情況同樣會出現在字符串常量(其實它也是數組常量)中。從而不是返回一對指針,而是試圖返回一對數組,由於數組類型不是可複製構造 的,因此代碼將無法編譯。一個解決的辦法是,將傳給 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);
}

這裡,如果被推斷的類型是數組,將會自動退化為指針,就像在標準綁定器和適配器中發生的一樣:理論上是"包裝"一個被推斷類型的任何函數。注意,make_pair 函數的參數並不以 call_traits 傳遞:這樣做可以防止從函數推斷模板參數。

例 4 (優化填充):

call_traits 模板可以 "優化" 作為函數參數傳遞的小的內建類型,這主要在該參數在一個循環體中使用時有作用。在下例中(見 fill_example.cpp),以兩種方法優化 std::fill 的一個版本:如果傳入的類型是一個單字節內建類型,則使用 std::memset 來提高填充的效率,否則使用傳統的C++實現,但使用 call_traits 來 "優化" 傳入的參數:

namespace detail{

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)
{
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 detail::filler<can_opt> filler_t;
filler_t::template do_fill<I,T>(first, last, val);
}

註:之所以說這對於小的內建類型是"最優的",是因為用 "T const" 替代 "const T&" 來傳遞值,可以告訴編譯器該值是常量且不是別名。有了這些信息,編譯器就可以將這個傳入的值緩存在寄存器中,解開循環,可使用並行指令:如果它們被支持的 話。你可以從中得到多少好處取決於你的編譯器 - 我們可以用 boost 中的一些精確的基準軟件來驗證這一點。

注意,fill 函數的參數沒有以 call_traits 傳遞:這樣做可以防止從函數推斷模板參數。fill 擔當了一個 "瘦包裝" 的角色,其中執行模板參數推斷,編譯器將優化對 fill 的調用,代之以對 filler<>::do_fill 的調用,後者使用了 call_traits.

原理

以下說明簡要地描述了在 call_traits 中所作出的抉擇背後的原理。

所有用戶自定義類型遵循 "現有習慣",無需任何說明。

小的內建類型(在標準中稱為基本類型[3.9.1])與現有習慣的差別僅在於 param_type typedef. 在這種情況下,傳遞 "T const" 是兼容於現有習慣的,但有些情形可以提升性能(見 Example 4),任何情況下都不會比 existing practice 更差。

指針與小型內建類型的原理相同。

對於引用類型,相關原理遵循 Example 2 - 引用的引用是不允許的,所以必須定義 call_traits 的成員以使得這些問題不會發生。現在有一個提議是將語言標準修改為 "引用的引用是一個引用" (issue #106, 由 Bjarne Stroustrup 提交), 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); // 無法編譯,由於 T 是一個數組。
}

通過使用 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); // 即使 T 為數組類型也可以
}

對於 value_type (以值方式返回),同樣只會返回指針,而不是整個數組的拷貝,同樣是 call_traits 實施了顯式的退化。當一個數組必須退化為指針時,value_type 成員就有用了 - Example 3 提供了測試案例(註:以數組特化 call_traits 是所有 call_traits 特化中最不好懂的,如果給定的語義引起某些問題,或者不能解決與數組相關的問題,我希望能收到相關的反饋。多數人可能從不需要這種特化)。


Revised 01 September 2000

© Copyright boost.org 2000. Permission to copy, use, modify, sell and distribute this document is granted provided this copyright notice appears in all copies. This document is provided "as is" without express or implied warranty, and with no claim as to its suitability for any purpose.

Based on contributions by Steve Cleary, Beman Dawes, Howard Hinnant and John Maddock.

Maintained by John Maddock, the latest version of this file can be found at www.boost.org, and the boost discussion list at www.yahoogroups.com/list/boost.

.