函数子,函数子类,函数及其它:38~42

第38条:遵循按值传递的原则来设计函数子类

  • 不能将函数作为参数传递给另一个函数,只能传递函数指针;
void qsort(void* base, size_t nmemb, size_t size, int(*cmpfcn)(const void*, const void*));//qsort的第三参数cmpfcn是按值传递的函数指针
  • 函数指针以及STL函数对象作为函数的参数和返回值时,习惯通过pass by value传递;
  • pass by value的缺点:
    ①传递大对象效率低(空间时间都影响);
    ②在继承体系下,可能产生剥离问题

解决方法1: pass by reference(不行,有的STL配接器和算法在接受函数对象作为参数时,本身就使用了传引用,若再将模板参数指定为传引用形式,则会出现“引用的引用”,会造成编译错误);因此没有必要传引用,依然可以按照习惯传值,只要设法克服效率和剥离问题,使函数子以正确的方式拷贝复制;

//for_each的原型
template<class InputIterator, class Function>
Function for_each(InputIterator first, InputIterator last, Function f);

//函数子类
class DoSomething : public unary_function<int, void>{
public:
	void operator()(int x){...}
};

deque<int> di;
...
//通过实例化函数模板参数DoSomething&, 强行将函数子按引用传递并返回;此处的示例for_each是可以的
for_each<deque<int>::iterator, DoSomething&>(di.begin(), di.end(), DoSomething());
//但下面这个STL配接器not1,在接收函数对象参数时const Predicate&,已经采用了引用,则实例化not1模板时不能再指定引用
template<class Predicate>
unary_negate<Predicate> not1(const Predicate& Pred);

not1<DoSomething&>(DoSomething());//则相当于传入not1的参数类型变成const DoSomething&&,“引用的引用”会编译错误;

解决方法2:优化pass by value(采用桥接模式,将该函数子类的数据和虚函数放入一个新的类,而该函数子类中只需要留一个指向新类对象的指针即可,这样本身小巧且单态,但确可访问大量数据且具备多态特性);
注:下面的桥接模式没有写拷贝构造函数,因为对桥接模式没理解,日后补上;

//效率低 + 存在剥离问题的函数子类BPFC
template<typename T>
class BPFC : public unary_function<T, void>{
private:
	Widget w;
	int x;
	double y;
	...  //数据成员很多,因此按值传递BPFC函数对象效率很低;
public:
	virtual void operator()(const T& val); //虚函数,存在剥离问题
	...
};

//使用桥接模式优化后的函数子类BPFC
template<typename T>
class BPFCImpl : public unary_function<T, void>{
private://不懂为什么所有数据成员 + 析构函数 + operator() + friend全都放在private;
	Widget w;
	int x;
	double y;
	...  //原本BPFC的所有数据成员
	virtual void operator()(const T& val)const; //判别式类的判别式函数要保证是纯函数,即operator()要声明为const,但这还不够,具体看第39条
	virtual ~BPFCImpl(); //此处不懂;
	friend class BPFC<T>;//允许BPFC调用private中的operator();
};

//现在优化过的BPFC class变的小巧(只含一个指针数据成员) 且 单态(不含虚函数成员);
template<typename T>
class BPFC : public unary_function<T, void>{
private:
	BPFCImpl<T>* p; //只留指向新类的一个指针
public:
	void operator()(const T& val){//变成了非虚函数,作为调用真正operator()的接口;
		p->operator()(val);
	}
};

第39条:确保判别式是“纯函数”

  • 判别式:返回值为bool (或可以隐式转换为bool)的函数;
    例如默认情况下,标准关联容器调用<操作符作为比较函数,当然也可以自定义一个比较函数;
    判别式常作为STL算法的参数传递(但往往还需要用ptr_fun加以修饰);
  • 判别式类:函数子类,其operator()的返回值为bool;
    判别式类的对象(即一个函数子)可以作为STL算法的参数传递;
  • 纯函数:指函数的返回值仅仅依赖于参数;
    即该函数只能访问参数和常量,才能保证在不同时间调用该函数时,不会产生不同的结果;该函数不能访问任何可变的变量以免修改,包括各种形式的非const对象,const类的mutable数据成员等等;
  • 之所以要求判别式是“纯函数”,因为判别式或判别式类的对象通过pass by value的方式,在STL的各种函数,算法之间传递。在各式各样的算法和函数实现中下,都能正确的传递函数对象,保证函数对象即使在不同时间被多次调用,都不会改变什么东西以免我们使用“自以为改变后的变量”,而其实"在下一次调用时pass by value得到的那个函数对象并不是我们以为的那个改变后的函数对象";
//remove_if的某种实现,其第三参数是一个函数对象,即(可能通过ptr_fun修饰的判别式)或(判别式类的对象)
template<typename FwdIterator, typename Prediate>
FwdIterator remove_if(FwdIterator begin, FwdIterator end, Prediate p){
	begin = find_if(begin, end, p);//p是传入remove_if的参数p的副本
	if(begin == end) return begin;
	return remove_copy_if(++next, end, begin, p);//我们以为这里传入的p是前面find_if修改过的p,其实这个p仍然是传入remove_if的参数p的副本
}

//一个错误的判别式类:其operator()函数不是“纯函数”
class BadPredicate : public unary_function<Widget, bool>{
public:
	BadPredicate() : timesCalled(0){}
	bool operator()(){
		return ++timesCalled == 3;
	}
private:
	size_t timesCalled;
};

vector<Widget> vw;
...
vw.erase(remove_if(vw.begin(), vw.end(), BadPredicate()), vw.end());//此处调用,并不是我们的预期的结果
  • STL中,判别式和判别式类的对象都可以互相用,只是判别式还需要用ptr_fun修饰一下,使其转化为functor;
  • best practice:对于判别式 或 **判别式类的operator()**这些函数,都要设计成“纯函数”;除了判别式类的operator()要声明为const之外,保证这些函数不能访问任何可变的变量,只能访问传入的参数和常量对象;

第40条:若一个类是函数子类,则应使它可配接

  • 在STL中,作为算法参数或返回类型传递的函数对象,为了使其更具通用性,应使它可配接:需要使其成为具有必要类型定义的仿函数;
  • 一个具有可配接性的函数子类:
  1. 继承自基结构unary_funciton或binary_function,具体继承哪个视该函数子类的operator()的参数个数而定:1个参数继承unary_function,两个参数继承binary_function
  2. unary_funciton或binary_function使STL的模板,需要指定模板参数:operator()的参数或返回类型不含指针时,则省略常量关键字const和引用&;若含指针,则原样抄;
  3. 根据需要合理使用配接器not1, not2, bind1st, bind2nd,修改一下原本的函数子功能以达到我们想要的仿函数;
  4. 配接器的参数至少需要一个仿函数:
    not1, not2的参数需是仿函数(若是普通函数,需要用ptr_fun()转化一下);
//普通函数isInteresting
bool isInteresting(const Widget* pw);

list<Widget*> widgetPtrs;
auto i = find_if(widgetPtrs.begin(), widgetPtrs.end(), isInteresting);  //正确,传递一个普通函数指针
auto i = find_if(widgetPtrs.begin(), widgetPtrs.end(), not1(isInteresting)); //错误,普通数指针不能作为配接器not1的参数
auto i = find_if(widgetPtrs.begin(), widgetPtrs.end(), not1(ptr_fun(isInteresting)));  //正确,ptr_fun将函数指针转化为仿函数

bind1st, bind2nd的两个参数至少一个是仿函数(可以实现将一个仿函数和另一个仿函数/一个值/一个普通函数结合得到我们想要的新仿函数)

[四种配接器](https://blog.csdn.net/jiuri1005/article/details/109692075)
  1. 除特殊场合,都应在每个函数子类中只写一个operator()成员函数,使其具有可配接性;
  • 若一个函数子类的所有成员都是共有的,例如仅有一个operator(),则将该函数子类声明为struct;
    若函数子类中还包含一些状态信息(如一些private数据成员),则往往声明为class;

第41条:理解ptr_fun, mem_fun, mem_fun_ref的来由

  • 对x调用函数f的三种情况:
    f是x的类型的非成员函数:① f(x);
    f是x类型的成员函数:②x.f(); ③p是指向x的指针,则p->f();
  • STL的算法,函数或者函数对象在被调用时,只支持非成员函数的语法形式
//下面为for_each的函数模板,可知for_each只支持f(x)形式的调用
template<typename InputIterator, typename function>
function for_each(InputIterator begin, InputIterator end, function  f){
	while(begin != end) f(*begin++); //对迭代器范围内的所指的元素,依次作为f的参数
}
//f是Widget的非成员函数,通过语法①直接调用
void f(Widget &){ 
	...
}
vecotr<Widget> vw;  //vector中存储Widget对象
for(vm.begin(), vw.end(), f);   //①类型的调用:f(Widget),对迭代器范围内的每一个Widget对象w都调用f(w);

//f是Widget的成员函数,通过mem_fun_ref_t将语法②转化为语法①直接调用
class Widget{
public:
	...
	void f(){}; //*this调用其成员函数f
}
vecotr<Widget> vw;  //vector中存储Widget对象
for(vm.begin(), vw.end(), &Widget::f);  //错误,无法通过编译
//将Widget::f传入mem_fun_ref,返回一个类型为mem_fun_ref_t的对象;for_each通过语法①对迭代器范围内的每个Widget对//象调用mem_fun_ref_t对象,mem_fun_ref_t对象再通过语法③调用真正的Widget::f函数
for(vm.begin(), vw.end(), mem_fun_ref(&Widget::f)); //&可以省略

//f是Widget的成员函数,通过mem_fun_t将语法③转换为语法①
class Widget{
public:
	...
	void f(){}; 
}
vecotr<Widget*> vw;  //vector中存储Widget*对象
for(vm.begin(), vw.end(), &Widget::f);  //错误,无法通过编译
//将Widget::f传入mem_fun,返回一个类型为mem_fun_t的对象(该对象保存着该成员函数的指针,并提供operator()函数,operator()调用通过参数传递进来的Widget对象的成员函数);
//实际调用过过程:for_each通过语法①对迭代器范围内的每个Widget*对象调用mem_fun_t对象,mem_fun_t对象再通过语法③调用Widget*的成员函数Widget::f();
for(vm.begin(), vw.end(), mem_fun(&Widget::f)); //&可以省略
  • mem_fun_t和 mem_fun_ref_t称为函数对象配接器;
  • 每当需要将一个成员函数传递给一个STL的组件时,就要使用 mem_fun_t或 mem_fun_ref_t;用于将对成员函数的调用语法转换为对非成员函数的调用语法,以此来适应算法
  • 指针容器支持多态,但对象容器不支持多态

第42条:确保less与operator<具有相同的意义

  • c++中,less实际调用的operator<,这是共识,因此不要修改less的实现;
  • 关键字不要乱用,如果用到了less,就要保证它的实现和调用operator<是同样的效果,这是大家默认的;
  • 如果想自定义一种与less不同的排序方式,就换个名字重新写一个新的函数子类,不要试图修改less的实现以达成自己预期的排序方式,会误导其他人;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值