函数对象
C++的STL大量算法实现中有许多需要进行运算的参数,如sort算法进行比较的方式、power进行运算的操作等。这些参数虽然可以使用一个函数指针的方式,将用户定义的特定操作以指针方式传入,但是函数指针不能满足抽象性的要求,而且无法与其他组件进行复杂的配接,因此诞生了函数对象。
函数对象
行为类似函数的一种对象,就实现来看,就是一个重载了函数运行操作符(operator())的类。
STL的函数对象执行某种特定的操作,基本覆盖了C++提供的所有操作符,包括算术类、关系类、逻辑类三种(C++11提供了三个按位运算函数对象bit_and
、bit_or
、bit_xor
)。从操作数的个数上看可分为一元与二元两类,C++11之前只提供了一元和二元运算,但C++11支持了可变模版参数,因而可以让用户自定义任意参数的函数对象,同时也覆盖了STL提供的运算符的函数对象。以下仅以C++11之前的函数对象来讨论。
接口规则
STL定义了函数对象的两个基类unary_function
与binary_function
,分别定义了一元与二元函数对象的接口规则。
template<typename Arg, typename Result>
struct unary_function{
typedef Arg argument_type;
typedef Result result_type;
};
template<typename Arg1, typename Arg2, typename Result>
struct binary_function{
typedef Arg1 first_argument_type;
typedef Arg2 second_argument_type;
typedef Result result_type;
};
所有函数对象定义继承自上述两个基类即可符合STL的接口规则。这对于后续进行函数对象适配是非常重要的。
函数对象示例
//一元运算函数对象
template<typename T>
struct negate : public unary_function<T, T>
{
T operator()(const T & x){ return -x;}
//继承自unary_function并传入两个模版参数
//返回值类型与参数类型相同
//函数功能就是取传入参数的相反数
};
//二元运算函数对象
tempalte<typename T>
struct logical_and : public binary_function<T, T, bool>
{
bool operator()(const T& x, const T& y)
{
return x && y;
}
//继承自binary_function,两个参数类型相同
//返回值类型为bool
};
除了上述针对运算符提供的函数对象外,用户也可以自定义特殊功能的函数对象,如果是C++11则可以使用可变模版参数。
//此函数为证同所用,元素经过运算之后不会有任何变化
template<typename T>
struct identity : public unary_function<T, T>
{
const T& operator()(const T& x) { return x; }
};
//针对map的pair对象,可以使用此函数选择第一个元素
template<typename Pair>
struct select1st : public
unary_function<Pair, typename Pair::first_type>
{
const typename Pair::first_type & operator()
(const Pair& x){ return x.first; }
};
元函数
针对编译期间的计算而诞生的元函数,从形式上看与函数对象的样子相似,但由于是在编译期,所操作的都是类型, 而编译期仅能支持整形常量值的运算,因此对于整形操作提供了类似STL函数对象的元函数。
整形外覆器
对于编译期间的运算必须是类型,故有如下整数类型的包装类:
template<class T, T N>
struct integer{
//N为编译期间确定的常量,也就是该整形外覆器的值
static const T value = N;
typedef integer<T, N> type;
typedef T value_type;
typedef integer<T, N+1>::type next;
typedef integer<T, N-1>::type prior;
//提供显式类型转换操作符,将当前类型转换为对应的整数类型T
operator T() const { return N; }
};
对于上述泛型的整数包装类,可以使用如下方式进行编译期的类型运算:
equal_to<ingeter<int, 10>, integer<int, 1> >::value
//equal_to为下面介绍的相等运算的元函数
操作示例
针对上述整形包装类,就得到了如下进行编译期运算的一系列元函数,分别有算术、位运算、关系、逻辑等操作,这些操作与对应于运行期的函数对象有着非常相似的形式。
//算术运算
template<typename T1, typename T2>
struct multiplies{
//common需要返回T1和T2运算后的范围更大的类型,需使用特化实现
typedef common<T1::type,T2::type>::type value_type;
static const value_type value = T1::value * T2::value;
typedef multiplies<T1,T2> type;
operator value_type(){ return value; }
};
//关系运算
//由于所有关系运算的结果都是bool类型,因此继承自bool_包装类实现
template<bool x>
struct bool_ {//bool包装类
static const bool value = x;
typedef bool value_type;
typedef bool_<x> type;
operator bool(){ return x; }
};
template<typename N1, typename N2>
struct equal_to : public bool_< N1::value == N2::value >
{};
//逻辑运算
template<typename N1, typename N2>
struct or_ : public bool_< N1::value && N2::value >
{};
与函数对象类似,整数操作元函数也可以进行证同操作、类型判断等:
template<class T>
struct identity{//证同,返回自身
typedef typename T::value_type value_type;
typedef typename T::type type;
static const value_type value = T::value;
};
//偏特化版本实现引用类型判断
template<class T> struct is_reference : bool_<false>{};
template<class T> struct is_reference<T &> : bool_<true>{};
经过上述比较,可以发现在编译期和运行期都能将运算操作进行函数化(函数对象和元函数),这种方式对于构建高可扩展的程序非常有效。