Effective STL 笔记-第6章 函数子、函数子类、函数及其他
6 函数子、函数子类、函数及其他
第 38 条:遵循按值传递的原则来设计函数子类。
函数指针是按值传递的。
函数对象往往按值传递和返回。所以,编写的函数对象必须尽可能地小巧,否则复制的开销大;函数对象必须是单态的(不是多态),不得使用虚函数。
如果你希望创建一个包含大量数据并且使用了多态性的函数子类,该怎么办呢?
template<typename T>
class BPFC: //BPFC = Big Polymorphic
public // Functor Class
unary_function<T, void> {
private:
Widget w; // 包含大量数据,按值传递效率低
int x;
...
public:
virtual void operator() (const T& val) cosnt; // 虚函数存在剥离问题
}
那么你应该创建一个小巧、单态的类,其中包含一个指针,指向另一个实现类,并且将所有的数据和虚函数都放在实现类中(“Pimpl Idiom”)。
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);
}
}
第 39 条:确保判别式是 “纯函数”。
判别式(predicate):一个返回值为 bool 类型的函数。
纯函数:指返回值仅仅依赖于其参数的函数。
判别式类(predicate class):一个函数子类,它的 operator() 函数是一个判别式(返回 true 或 false)。
STL 中凡是可以接受一个判别式类对象的地方,也就可以接受一个判别式函数。
判别式应该是一个纯函数,而纯函数应该没有状态。
第 40 条:若一个类是函数子,则应使它可配接。
对函数指针,要先应用 ptr_fun 之后再应用 not1 之后才可以工作。
4 个标准的函数配接器(not1、not2、bind1st、bind2nd)都要求一些特殊的类型定义,提供这些必要类型定义(argument_type、first_argument_type、second_argument_type、result_type)的函数对象被称为可配接(adaptable)的函数对象。
提供这些类型定义最简单的方法:让函数子从一个基结构继承。
- 对于 unary_function,必须指定函数子类 operator() 所带的参数类型,以及 operator() 返回类型。
- 对于 binary_function,必须指定 3 个类型:operator() 第一个和第二个参数类型,以及 operator() 返回类型。
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 和应用(&)部分。
第 41 条:理解 ptr_fun、mem_fun 和 mem_fun_ref 的来由。
STL语法惯例:函数或者函数对象被调用时,总是使用非成员函数的语法形式。
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。
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。
第 42 条:确保 less<T>
于 operator< 具有相同的语义。
尽量避免修改 less 的行为,可能会误导其他程序员。
如果你使用了 less,无论是显式地还是隐式地,都需要确保它于 operator< 具有相同的意义。
如果你希望以一种特殊的方式来排列对象,那么最好创建一个特殊的函数子类。