Effective STL 精度总结 [6] | 仿函数、仿函数类、函数等

警告
本文最后更新于 2023-09-23,文中内容可能已过时。

前言

Effective-STL总结系列分为七部分,本文为第六部分,涉及原书第六章,内容范围Rule38~42。为方便书写,Rule38简写为R38。

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

函数指针是按值传递的。

函数对象往往按值传递和返回。所以,编写的函数对象必须尽可能地小巧,否则复制的开销大;函数对象必须是单态的(不是多态),不得使用虚函数。

如果你希望创建一个包含大量数据并且使用了多态性的函数子类,该怎么办呢?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template<typename T>
class BPFCImpl:
	public unary_function<T, void> {
private:
	Widget w;
	int x;
	...
	virtual ~BPFCImpl();
	virtual void operator() (const T& val) const;
friend class BPFC<T>;					// 允许BPFC访问内部数据。
}

template<typename T>
class BPFC:								// 新的BPFC类:短小、单态
	public unary_function<T, void> {
private:
	BPFCImpl<T> *pImpl;					// BPFC唯一的数据成员
public:
	void operator() (const T& val) const	// 现在这是一个非虚函数,将调用转到BPFCImpl中
    {
        pImpl->operator()(val);
    }
}

那么你应该创建一个小巧、单态的类,其中包含一个指针,指向另一个实现类,并且将所有的数据和虚函数都放在实现类中(“Pimpl Idiom”)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template<typename T>
class BPFCImpl:
	public unary_function<T, void> {
private:
	Widget w;
	int x;
	...
	virtual ~BPFCImpl();
	virtual void operator() (const T& val) const;
friend class BPFC<T>;					// 允许BPFC访问内部数据。
}

template<typename T>
class BPFC:								// 新的BPFC类:短小、单态
	public unary_function<T, void> {
private:
	BPFCImpl<T> *pImpl;					// BPFC唯一的数据成员
public:
	void operator() (const T& val) const	// 现在这是一个非虚函数,将调用转到BPFCImpl中
    {
        pImpl->operator()(val);
    }
}

R39 确保判别式是 “纯函数”

判别式(predicate):一个返回值为 bool 类型的函数。

纯函数:指返回值仅仅依赖于其参数的函数。

判别式类(predicate class):一个函数子类,它的 operator() 函数是一个判别式(返回 true 或 false)。

STL 中凡是可以接受一个判别式类对象的地方,也就可以接受一个判别式函数。

判别式应该是一个纯函数,而纯函数应该没有状态。

R40 使仿函数类可适配

对函数指针,要先应用ptr_fun之后再应用not1之后才可以工作。

4 个标准的函数配接器(not1not2bind1stbind2nd)都要求一些特殊的类型定义,提供这些必要类型定义(argument_typefirst_argument_typesecond_argument_typeresult_type)的函数对象被称为可配接(可适配)(adaptable)的函数对象。

提供这些类型定义最简单的方法:让函数子从一个基结构继承。

  • 对于 unary_function,必须指定函数子类 operator() 所带的参数类型,以及 operator() 返回类型。
  • 对于 binary_function,必须指定 3 个类型:operator() 第一个和第二个参数类型,以及 operator() 返回类型。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
template<typename T>
class MeetsThreshold: public std::unary_function<Widget, bool> {
private:
    const T threshold;						// 包含状态信息,使用类封装。
public:
    MeetsThreshold(const T& threshold);
    bool operator()(const Widget&) const;
    ...
}

struct WidgetNameCompare:					// STL中所有无状态函数子类一般都被定义成结构。
	public std::binary_function<Widget, Widget, bool> {
	bool operator()(const Widget& lhs, const Widget& rhs) const;
}

注意,一般情况下,传递给 binary_function 或 unary_function 的非指针类型需要去掉 const 和应用(&)部分。

R41 理解 ptr_fun、mem_fun 和 mem_fun_ref 的来由

STL语法惯例:函数或者函数对象被调用时,总是使用非成员函数的语法形式。

1
2
3
4
5
6
for_each(vw.begin(), vw.end(), test);					// 调用1:f(x),f为非成员函数
for_each(vw.begin(), vw.end(), &Widget::test);			// 调用2:x.f(),f为成员函数
														// x是一个对象或对象的引用
list<Widget *> lpw;
for_each(lpw.begin(), lpw.end(), &Widgettest);			// 调用3:p->f(),f为成员函数
														// p是一个指向对象x的指针。

mem_fun、mem_fun_t:mem_fun 将语法 3 调整为语法 1。

1
2
3
template<typename R, typename C>		//该mem_fun声明针对不带参数的非const成员函数
mem_fun_t<R,C>							//C是类,R是所指向的成员函数返回的类型。
mem_fun(R(C::*pmf));

mem_fun 带一个指向某个成员函数的指针参数 pmf,并且返回一个 mem_fun_t 类型的对象。

mem_fun_t 是一个函数子类,它拥有该成员函数的指针,并提供了 operator() 函数,在 operator() 中调用了通过参数传递进来的对象上的该成员函数。

类似地,mem_fun_ref 将语法 2 调整为语法 1。

总结:

  • std::ptr_fun:将函数指针转换为函数对象。
  • std::mem_fun:将成员函数转换为函数对象(指针版本)。
  • std::mem_fun_ref:将成员函数转换为函数对象(引用版本)。

R42 确保less与operator<具有相同的语义

尽量避免修改 less 的行为,可能会误导其他程序员。

如果你使用了 less,无论是显式地还是隐式地,都需要确保它与 operator< 具有相同的意义。

如果你希望以一种特殊的方式来排列对象,那么最好创建一个特殊的函数子类。

假设有一个multiset容器,它默认的比较函数是less,而less在默认情况下会调用operator<来完成multiset的排序,而operator<是按照Widget中成员变量weight来排序的,现在特殊情况下需要一个按Widget中成员变量speed来排序的multiset,一种方法是全特化less,但是这种做法并不好,因为用户可能是觉得自己按照weight来排序,但是其实做的却是按照speed来排序,更好的办法是创建一个函数子类,然后用该子类做比较函数,而不是改变less的默认行为。

1
2
3
4
5
6
7
struct speedCompare : public binary_function<Widget, Widget, bool> {
bool operator()(const Widget& lhs, const Widget& rhs) const{
        return lhs.maxSpeed() < rhs.maxSpeed();
    }
};

multiset<Widget, speedCompare> widgets;

Ref:

[1]. https://www.cnblogs.com/Sherry4869/p/15162253.html [2]. https://blog.csdn.net/zhuikefeng/article/details/108164117#t35 [3]. https://zhuanlan.zhihu.com/p/458156007

Buy me a coffee~
支付宝
微信
0%