代码改变世界

《Effective STL 读书笔记》 第六章 函数子、函数子类、函数及其他

2011-09-03 20:54  咆哮的马甲  阅读(582)  评论(0编辑  收藏  举报
作者:咆哮的马甲
出处:http://www.cnblogs.com/arthurliu/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。 
转载请保持文档的完整性,严禁用于任何商业用途,否则保留追究法律责任的权利。

第三十八条 遵循按值传递的原则来设计函数子类

c和C++中 以函数指针为参数的例子,函数指针是按值传递的

1 void qsort(void* base, size_t nmemb, size_t size,
2
3 int(*cmpfcn)(const void *, const void *));

  

STL函数对象是对函数指针的抽象形式,在STL中函数对象在函数中的传递也是按值传递的。

for_each算法的返回值就是一个函数对象,它的第三个参数也是函数对象。

1 template<class InputIterator,
2 class Function>
3 Function //按值返回
4 for_each(InputIterator first, InputIterator second, Function f); //按值传递

  

因为STL函数对象按值传递的特性,所以在设计函数对象时要:

  1. 将函数对象要尽可能的小,以减少拷贝的开销。
  2. 函数对象尽量是单态的(不要使用虚函数),以避免剥离问题。


对于复杂的设计而言,具有包含很多信息的和含有继承关系的函数对象也可能难以避免,这时可以采用Bridge Pattern来实现

 1 template<typename T>
2 class functorImp :
3 public unary_function<T,void> {
4 private :
5 Widget w;
6 int x;
7
8 public :
9 virtual ~functorImp();
10 virtual void operator() (const T& val) const;
11 friend class functor<T>;
12 };
13
14 template<typename T>
15 class functor :
16 public unary_function<T,void> {
17 private:
18 functorImp<T> *pImp; //唯一的一个数据成员
19
20 public:
21 void operator() (const T& val) const
22 {
23 pImp->operator()(val); //调用重载的operator
24 }
25 };


函数对象本身只包含一个指针,而且是不含虚函数的单态对象。真正的数据和操作都是由指针所指向的对象完成的。

对于这个实现,要注意的是在函数对象拷贝的过程中,如何维护这个指针成员。既能避免内存泄漏而且可以保证指针有效性的智能指针是个不错的选择。

1 shared_ptr<functorImp<T> *> pImp;



第三十九条
确保判别式是纯函数
 

判别式的一些基本概念:

  •  判别式 - 返回值为bool类型或者可以隐式转换为bool类型的函数
  •  纯函数 - 返回值仅与函数的参数相关的函数
  •  判别式类 – operator()函数是判别式的函数子类。 STL中凡是能接受判别式的地方,就可以接受一个判别式类的对象。 


对于判别式不是纯函数的一个反例

 1 class Remove3rdElement 
2 : public unary_function<int,bool> {
3 public:
4
5 Remove3rdElement():i(0){}
6
7 bool operator() (const int&)
8 {
9 return ++i == 3;
10 }
11
12 int i;
13 };
14 ...
15 vector<int> myvector;
16 vector<int>::iterator it;
17
18 myvector.push_back(1);
19 myvector.push_back(2);
20 myvector.push_back(3);
21 myvector.push_back(4);
22 myvector.push_back(5);
23 myvector.push_back(6);
24 myvector.push_back(7);
25 myvector.erase(remove_if(myvector.begin(), myvector.end(), Remove3rdElement()),myvector.end()); // 1,2,4,5,7 remove_if之后的结果为 1,2,4,5,7,6,7。 返回值指向的是第六个元素。



第四十条
如果一个类是函数子,应该使它可配接
 

STL中四个标准的函数配接器(not1, not2, bind1st, bind2nd)要求其使用的函数对象包含一些特殊的类型定义,包含这些类型定义的函数对象称作是可配接的函数对象。下面的代码无法通过编译:

1 bool isWanted(const int i);
2
3 ...
4
5 vector<int> myvector;
6
7 vector<int>::iterator it = find_if(myvector.begin(), myvector.end(), not1(isWanted)); // error C2955: 'std::unary_function' : use of class template requires template argument list


从上面的错误可以看出,这个isWanted函数指针不能被not1使用,因为缺少了一些模板参数列表。ptr_fun的作用就在于给予这个函数指针所需要的类型定义从而使之可配接。

1 vector<int>::iterator it = find_if(myvector.begin(), myvector.end(), not1(ptr_fun(isWanted)));


这些特殊的类型定义包括: argument_type first_argument_type second_argument_type result_type,提供这些类型定义最简单的方式是是函数对象的类从特定的模板继承。

如果函数子类的operator方法只有一个实参,那么应该从unary_function继承;如果有两个实参,应该从binary_function继承。

对于unary_function和binary_function,必须指定参数类型和返回值类型。

 1 template<typename T>
2 class functor : public unary_function<int, bool>
3 {
4 public :
5 bool operator()(int);
6 };
7
8 template<typename T>
9 class functor2 : public binary_function<int, double, bool>
10 {
11 public :
12 bool operator()(int, double, bool);
13 };

 

对于operator方法的参数:

  • operator的参数如果是非指针类型的,传递给unary_function和binary_function的参数需要去掉const和引用&符号
  •  operator的参数如果是指针类型的,传递给unary_function和binary_function的参数要与operator的参数完全一致。



第四十一条
理解ptr_funmem_funmem_fun_reference的来由
 

对于ptr_fun在第40条已经有了一些介绍,它可以用在任何的函数指针上来使其可配接。

下面的例子,希望在myvector和myvector2的每一个元素上调用元素的成员函数。

 1 class Widget
2 {
3 public :
4 void test();
5 };
6
7 ...
8
9 vector<Widget> myvector;
10 vector<Widget*> myvector2;
11
12 ...
13
14 for_each(myvector.begin(),myvector.end(), &Widget::test); // 编译错误
15 for_each(myvector2.begin(),myvector2.end(), &Widget::test); //编译错误


而for_each的实现可能是这样的

1 template<typename InputIterator, typename Function>
2 Function for_each(InputIterator begin, InputIterator end, Function f)
3 {
4 while (begin != end)
5 f(*begin++);
6 } 


对于mem_fun和mem_fun_reference, 就是要使成员方法可以作为合法的函数指针传递

1 for_each(myvector.begin(),myvector.end(), mem_fun_ref(&Widget::test)); // 当容器中的元素为对象时使用mem_fun_ref
2
3 for_each(myvector2.begin(),myvector2.end(), mem_fun(&Widget::test)); // 当容器中的元素为指针时,使用mem_fun


那么mem_fun是如何实现的呢?

1 template<typename R, typename C>
2 mem_fun_t<R,C>
3 mem_fun(R(C::*pmf)());


mem_fun接受一个返回值为R且不带参数的C类型的成员函数,并返回一个mem_fun_t类型的对象。mem_fun_t是一个函数子类,拥有成员函数的指针,并提供了operator()接口。operator中调用了通过参数传递进来的对象上的成员函数。



第四十二条
确保less<T>operator<具有相同的语义
 

STL规定,less总是等价于operator<, operator<是less的默认实现。

应当尽量避免修改less的行为,而且要确保它与operator<具有相同的意义。如果希望以一种特殊的方式来排序对象,那么就去创建一个新的函数子类,它的名字不能是less.