<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
数组的常量引用”,这也适用于字符串字面量(实际上是数组字面量)。因此,它不会返回一对指针,而是尝试返回一对数组,并且由于数组类型不可复制构造,因此代码无法编译。一种解决方案是显式地将参数强制转换为 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
表达的:这样做会阻止模板参数推导正常工作。相反,fill 充当“薄包装器”,用于执行模板参数推导,编译器将完全优化掉对 fill 的调用,并将其替换为对 filler<>::do_fill
的调用,该调用确实使用了 call_traits
。
以下注释旨在简要描述 call_traits
中所做选择背后的基本原理。
所有用户定义类型都遵循“现有做法”,无需评论。
小型内置类型(标准称之为 基本类型)与现有做法的不同之处仅在于 param_type
typedef
。在这种情况下,传递 T const
与现有做法兼容,但在某些情况下可能会提高性能(参见 示例 4)。在任何情况下,这都不应该比现有做法更糟。
指针遵循与小型内置类型相同的基本原理。
对于引用类型,基本原理遵循 示例 2 - 不允许对引用的引用,因此必须定义 call_traits
成员,以避免出现这些问题。有一个提议修改语言,使“对引用的引用是引用”(问题 #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); // 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]>; }