part6 遍布STL的 functor, 通常应该可配接(adaptable)
, 并经 function Adapter
进一步配接
总结:
(1)让functor 继承自unary_function/binary_function
, 从而继承了它们提供的 特殊类型定义(typedef: argument_type/result_type/...)
, 从而成为可配接的(adaptable)
可配接的对象
经过function Adapter(函数配接器/适配器, not1/bind2nd/...)进一步配接(如 operator() 结果取反)
后, 返回的仍是可配接对象
(2) 而, 作STL算法参数
时, 相对于用函数(指针)
, 用 函数对象 会带来 抽象性利益
细节:
(1) 应用
[1] 关联容器
中用于排序
[2] STL算法
(for_each/transform/find_if等)中用于 操作参数
或 条件 (pred) 参数
(2)函数 配接器(function Adapter: not1/ bind2nd)
可动态地生成 functor
内部机制: 保存
其要配接的(预测式/返回bool的)可配接对象 pred
+ 转调用
可配接对象 pred 的 operator()
+ 取 逻辑非 !
client 对not1返回的可配接对象做 `opertor() 操作` 时,
会被导引`调用 其内部所含可配接对象pred的 opertor() 操作`,
再 `取 逻辑非 !`
template <class Predicate>
unary_negate<Predicate>
not1 (const Predicate& pred) // 模板函数, 对可配接对象, 取否定后返回的仍是可配接对象
{
return unary_negate<Predicate>(pred);
}
template <class Predicate>
class unary_negate
: public unary_function <typename Predicate::argument_type, bool>
{
protected:
Predicate pred_;
public:
explicit
unary_negate (const Predicate& pred)
: pred_ (pred) {}
bool
operator() (const typename Predicate::argument_type& x) const
{ return !pred_(x); }
};
(3) not1/ptr_fun 内部机制本质相同
, 不同的是, 保存的是 可配接的预测式 pred / 不可配接的 函数指针
item40: functor
/函数对象通常应该可配接
, 除非不用考虑与函数对象配接器(not1等)配接
可配接的函数对象能与其他STL组件更默契地协同工作
, 能用于更多的 context: 少代价, 带来多便利
1 ptr_fun 的引入: 指针容器 + 查找 + 否定含义的条件pred
方法: 先对条件/pred
用 ptr_fun 包装成函数对象
, 再用函数配接器 not1 取否定
即, not1( ptr_fun(pred) ): 条件 pred = 函数指针 isInteresting
例: 在 widgetPtrLst 中查找
第1个不满足条件 isInteresting()
的指针(Widget*)
bool isInteresting(const Widget* pw);
{
Widget wInterested(...);
return lhs.getInterestValue() < wInterested.getInterestValue();
}
list<Widget*>::iterator iter =
find_if(widgetPtrLst.begin(), widgetPtrLst.end(),
not1( ptr_fun(isInteresting) ) ); // isInteresting 是函数指针
if(iter != widgetPtrLst.end() )
// ...
2 ptr_fun 作用, 机制
(1) 作用: 完成最多3种特殊的类型定义(typedef)
, 供4个标准函数配接器(not1/not2/bind1st/bind2nd)
使用, 来实现可配接
特殊的类型定义(typedef)
argument_type
first_argument_type/second_
result_type
的子集
(2) 机制: ptr_fun 内部
[1] 继承自 std::unary_function 或 std::binary_function
, 以 继承其提供
的 函数对象配接器
所需的特殊类型定义(typedef)
指定 unary_/binary_ 的模板形参
为 ptr_fun 的 operator()
的 参数(去掉const+引用/不去掉const+指针 后的)类型 和 返回类型
一般规则: 指针作参数或返回类型
的functor, 传给 unary_function/binary_function 的类型
与 operator()
的参数和返回类型完全相同
[2] 保存
函数指针 + 其 operator() 转调用
函数指针的 operator()/call
template <class Arg, class Result>
pointer_to_unary_function<Arg,Result>
ptr_fun (Result (*pf)(Arg))
{
return pointer_to_unary_function<Arg,Result>(pf);
}
template <class Arg, class Result>
class pointer_to_unary_function
: public unary_function <Arg, Result>
{
protected:
Result(*pf)(Arg);
public:
explicit pointer_to_unary_function ( Result (*pf_)(Arg) )
: pf (pf_) {}
Result
operator() (Arg x) const
{ return pf(x); }
};
(3) ptr_fun 实现
是可配接functor 的范例
struct TNameCompare // 无状态 functor => 用 struct, 而非 class
: public std::binary_function<T, T, bool>
{
bool
operator()(const T& lhs, const T& lhs) const;
};
struct TPtrTNameCompare
: public std::binary_function<const T*, const T*, bool>
{
bool
operator()(const T* lhs, const T* rhs) const;
};
STL中所有 无状态 functor (如 less<T> / plus<T>等)
一般都被定义成 struct
(4) 函数指针 为什么不能直接用于 函数配接器 not1 等
? 答: 缺少函数配接器所需要的特殊类型定义
3 functor 含多个 函数调用操作符 operator()
, 则 最多只有1种调用形式 可配接
: 与 unary_/binary_ 匹配的 -> 半配接能力
的 functor -> 往往是设计错误
4 函数对象 / 函数指针 / 函数 的不同应用场合
(1) 否定含义的条件pred
的表达
[1] 用函数(指针): 不能直接用
, 需要用ptr_fun 包装
一层成为函数对象
如前
[2] 用函数对象
临时对象 Widget(...) lifetime
在 find_if 执行完才结束
, 保证算法内部可以用 const 引用使用它
list<Widget*>::iterator iter =
find_if(widgetPtrLst.begin(), widgetPtrLst.end(),
not1( IsInteresting<Widget>(Widget(...) ) ) );
functor 保存的对象
与 操作的对象 类型可以不同
, 只要能基于相同类型 提供比较语义(如 operator <)
// gcc4.9 C++98 下 OK, VS2019下OK
#include <list>
#include <functional>
#include <iostream>
#include <algorithm>
using namespace std;
class Widget
{
private:
int interestValue;
public:
Widget(int interestValue_) : interestValue(interestValue_) {}
Widget() : interestValue(0) {}
int getInterestValue() const { return interestValue; }
};
template<typename T>
struct IsInteresting
: public std::unary_function<const Widget*, bool> // 与 operator()(.) 参数和返回值类型要匹配
{
private:
const T& tInterested; // const T& tInterested;
public:
IsInteresting(const T& tInterested_)
:tInterested(tInterested_) {}
bool
operator()(const Widget* lhs) const // 也可以用 const T* lhs
{
return lhs->getInterestValue() == tInterested.getInterestValue();
}
};
int main()
{
list<Widget*> widgetPtrLst;
widgetPtrLst.push_back(new Widget(10));
widgetPtrLst.push_back(new Widget(3) );
widgetPtrLst.push_back(new Widget(2) );
list<Widget*>::iterator iter =
find_if(widgetPtrLst.begin(), widgetPtrLst.end(),
not1(IsInteresting<Widget>(Widget(10) ) ) );
std::cout << (*iter)->getInterestValue();
}
print
3
(2) 可以用函数对象, 也可以用函数指针
: STL算法的操作参数
为什么可以用函数指针?靠(模板)函数模板实参推断
, 可推断出操作实参(函数指针)的类型
(3) 只能用函数对象, 不能用函数(指针)
: 作模板形参
例: 比较函数对象作关联容器的模板形参
, 指定排列顺序
set<string, CIStringCompare> strSet; // item19
typedef set<string*, StringPtrDereferenceLess> StrPtrSet;
StrPtrSet strPtrSet; // item20
为什么不能用函数指针? (模板)类没有 模板实参推断机制
(4) 能用函数指针, 不能用函数(名)
: 可调用对象
为什么不能用函数? 函数不是对象/函数类型不能直接定义对象
1] 函数类型
不能直接 定义对象
2] 函数引用
可视为 const 函数指针
item41 ptr_fun/mem_fun/mem_fun_ref
1 由来: 在对象
上调用函数
<=> 用函数操作对象
2类3种方式, 本质相同: pObj->f() / obj.f()
最终被编译器编译为 f(pObj) f(&obj)
(1) 面向过程: 对象作函数的参数
(2) 面向对象: 函数作对象成员, 让对象自己去调用自己的函数
f(obj) // 语法1 -> ptr_fun
pObj->f(); // 语法2 -> mem_fun
obj.f() // 语法3 -> mem_fun_ref
2 功能
(1) 函数模板 ptr_fun/mem_fun
使函数(指针) /成员函数(指针) 可配接
(2) mem_fun/mem_fun_ref
用来调整(通过语法2/3被调用的)成员函数
, 使之能够通过语法1被调用
,
从而能用于STL算法
(for_each等), 因为STL算法通过语法1调用函数
template<typename ReturnType, class C>
mem_fun_t<ReturnType, C>
mem_fun(ReturnType (C::*pmf)() )
{ return mem_fun_t<S,T>(pmf); }
template <class S, class T>
class mem_fun_t : public unary_function <T*,S>
{
S (T::*pmem)();
public:
explicit mem_fun_t ( S (T::*p)() )
: pmem (p) {}
S operator() (T* p) const // operator() 的参数必须是裸指针
{
return (p->*pmem)();
}
};
mem_fn 的返回类型的 operator() 的参数可为任意类型
, 不再像 mem_fun 那样要求为裸指针
std::mem_fn 的返回类型
未指定, 但其 operator() 的参数可为任意类型
template<class... Args>
/* see below */
operator()(Args&&... args) /* cvref-qualifiers */
noexcept(/* see below */);
list<Widget*>::iterator iter =
find_if(widgetPtrLst.begin(), widgetPtrLst.end(),
ptr_fun(isInteresting) );
list<Widget*>::iterator iter =
find_if(widgetPtrLst.begin(), widgetPtrLst.end(),
mem_fun(&Widget::isInteresting) );
item42: 通常, 要确保 模板 less<T> 与 operator< 语义相同
原因: less<T>
实现默认调用 T 的 operator<
但是, 若想让 less<Widget> 调用 Widget 的另一成员函数
, 要对 less<T> 用 Widget 特化
例: 确保 智能指针的排序行为
与它们的内置指针
的排序行为相同
namespace std
{
template<typename T>
struct less<std::shared_prt<T> >
: public binary_function<std::shared_prt<T>, std::shared_prt<T>, bool>
{
bool
operator()(const std::shared_prt<T>& lhs,
const std::shared_prt<T>& lhs) const
{
return less<T*>() ( lhs.get(), rhs.get() );
}
};
}
item46本在 part7
item46 STL算法参数
考虑 函数对象 而不是函数(指针)
两大原因
1 STL算法操作参数若用函数对象
, 且其operator()能inline
成功, 则相对于用函数(指针)
会带来 抽象性利益(abstraction bonus)
原因: STL(模板)算法(如 sort )
在实例化过程(编译阶段)
中可以直接将函数对象的operator()的函数体inline
进去; 而函数指针参数抑制了内联
机制
算法内部通过指针发出的调用
是间接的函数调用
, 大多数编译器不会
试图对通过函数指针执行
的函数调用
进行内联
优化
(1) 内联: 类内实现=>隐式inline / 类外实现并用inline声明 => 显式inlime + 函数体短小 -> 才可能被编译器真正内联
(2) 函数
参数会被编译器
转化为函数指针
参数
(3) 抽象性代价
C++ 相对于 C通常
带来抽象性代价
C++ 操作含内置类型(如 double)成员数据的对象
比
C直接操作内置类型(如 dounle)数据
低效
(4) 抽象性利益的实例
[1] C++ STL算法用 函数对象参数 vs. 函数指针参数
inline
bool myGreater(double d1, double d2)
{
return d1 > d2;
}
struct MyGreater // 此例先不考虑可配接
{
bool myGreater(double d1, double d2) // 类内实现 => 隐含 inline, 且应该能 inline 成功
{
return d1 > d2;
}
}
sort(v.beign(), v.end(), MyGreater() );
<=> 用
sort(v.beign(), v.end(), std::greater<double>() );
比
sort(v.beign(), v.end(), myGreater);
快 160%(百万个double下)
##Note:
sort(v.beign(), v.end(), myGreater);
的 sort模板实例化时, 编译器生成的函数声明为
void sort(...,
bool (*comp)(double, double) );
[2] C++的sort) 比 C的 qsort 高效
第3参数 可以是函数对象/只能是函数指针
2 编译器实现 + STL实现
可能不完全符合C++标准
, 可能拒绝本合理的代码
例: 特定STL平台 处理const成员函数(如string::size)时, 用 mem_fun_ref 会报错
解决: 用函数对象