Effective STL笔记(2)-Functors, Functor Classes, Functions, etc.

函数对象在算法、容器方面用的很普遍,这部分作者给出了5个Item,着重强调如何以STL方式来使用这些函数对象。

Item38:Design functor classes for pass-by-value.

c/c++将函数作为参数传递时,传递的是函数指针,如我们看qsort的声明:
void qsort(void *base, size_t nmemb, size_t size, 
	int (*cmpfcn)(const void*, const void*));
函数参数cmpfcn将函数体拷贝(按值传递)给函数sqort。STL的约定是传给函数和从函数返回时函数对象是按值传递的。如果通过引用传递和返回,那么编译器可能不能通过,如下面我们对for_each指定显式模板实参:
for_each<DequeIntIter,	DoSomething&>(di.begin(), di.end(), d);
按值传递意味着我们的函数对象需要满足下两个条件:
1、函数对象精炼,较少拷贝花费。
2、非多态。若是多态那么在传递派生类的时候可能会发生派生类部分被切割。
当有时候无法避免的时,我们需要将数据和多态移到另一个类中,然后给你的函数对象一个指向这个新类的指针,如考虑下面这个例子:
template<typename T>
class BPFC : public unary_function<T, void> {	// BPFC = “Big Polymorphic
private:
	Widget w;					// too many members
	Int x;				
	...						
public:
	virtual void operator()(const T& val) const;	// virtual function,
	...				// cut off!
};
建立一个包含一个指向实现类的指针的小而单态的类,然后把所有数据和虚函数放到实现类:
template<typename T>					
class BPFCImpl
	public unary_function<T, void> {		
private:
	Widget w;					// all members
	int x;						// 
	...
	virtual ~BPFCImpl();
	virtual void operator()(const T& val) const;
	friend class BPFC<T>;
};

template<typename T>
class BPFC : public unary_function<T, void> { // monomorphic
private:
	BPFCImpl<T> *pImpl;	 // pointe to BPFCImpl	
public:
	void operator()(const T& val) const // nonvirtual
	{						
		pImpl->operator() (val);
	}
	...
};
上面的实现方式就是所谓的”Bridge模式“。由于上诉函数对象含有指针类型,所以必须考虑复制、拷贝、析构函数做了正确的事情。

Item39:Make predicates pure functions.

纯函数是返回值只依赖参数的函数,如果predicate不是纯函数,那么它在算法中起到的作用可能和我们想的不一样。如考虑下面这个predicate class,它在第三次被调用的时候返回true:
class BadPredicate : public unary_function<Widget, bool> {	
public:
	BadPredicate(): timesCalled(0) {}		
	bool operator()(const Widget&)
	{
		return ++timesCalled == 3;
	}
private:
	size_t timesCalled;
};
我们将它应用于vector:
vector<Widget> vw;				
vw.erase(remove_if(vw.begin(), vw.end(), BadPredicate()), 
			vw.end()); //// remove the third Widget;
这段代码表面上看没有问题,但是很多STL实现不仅删去第三个对象,还会删去第六个对象!我们来分析一下可能的一种remove_if实现:
template <typename FwdIterator, typename Predicate>
FwdIterator remove_if(FwdIterator begin, FwdIterator end, Predicate p)
{
	begin = find_if(begin, end, p);
	if (begin == end) return begin;
	else {
		FwdIterator next = begin;
		return remove_copy_if(++next, end, begin, p);
	}
}
最开始p的timesCalled初始化为0,然后 按值传递 给find_if,在find_if中执行了三次,控制权转移到remove_copy_if,而此也是将p拷贝给它, 而timeCalled仍为0,在remove_copy_if第三次调用predicate时也会返回ture。
所以我们必须将predicate设计成纯函数,若是在函数对象中,operator()必须是const的。

Item 40. Make functor classes adaptable.

标准函数适配器(not1、not2、bind1st和bind2nd)需要某些typedef(argument_type、first_argument_type、second_argument_type和result_type),我们传递函数给他们时必须让其可适配。考虑如下代码:
bool isInteresting(const Widget *pw);
list<Widget*>::iterator i = find_if(widgetPtrs.begin(), 
	widgetPtrs.end(), not1(ptr_func(isInteresting)));
为了让isInteresting可适配,我们在前面加上ptr_func。
对于函数对象,我们通常根据实参个数继承std::unary_function或者std::binary_function。他们都是类模板,我们要指定参数实例化他们才能继承,除了相应的参数,我们还要指定返回类型,如下面的代码:
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;
	...
};
一般来说传递给他们的非指针类型都是非const非引用的,带有或返回指针的仿函数的一般规则是传给unary_function或binary_function operator()带有或返回的类型。最后确保operator()没有被重载,意外的情况会让其失去可适配性。

Item41:Understand the reasons for ptr_fun, mem_fun, and mem_fun_ref.

STL中函数和函数对象总使用用于非成员函数的语法形式调用,当我们的一类predicate为一个类的成员函数时,我们就需要使用这些来将其转换成函数对象让算法调用。如下面代码:
list<Widget*> lpw;
...
for_each(lpw.begin(), lpw.end(), mem_fun(&Widget::test));
list<Widget> lpw;
...
for_each(lpw.begin(), lpw.end(), mem_fun_ref(&Widget::test));
同时,mem_fun和mem_fun_ref都提供了一些typedef。
当一个成员函数传递给STL组件时,必须使用mem_fun或者mem_fun_ref,对于非成员函数当我们不确定是否需要ptr_fun是,总使用ptr_fun,这样子没有开销。

Item42:Make sure less<T> means operator<.

假设一个Widget有最高速度和重量,我们想建立一个按照最高速度排序Widget的multiset<Widget>。multiset<Widget>的默认比较函数是less<Widget>,而且默认的less<Widget>通过调用Widget的operator<来工作,一种实现方式是通过特化less<T>:
template<> struct std::less<Widget> // specialization
	: public std::binary_function<Widget, Widget, bool> {
	bool operator()(const Widget& lhs, const Widget& rhs) const
	{
		return lhs.maxSpeed() < rhs.maxSpeed();
	}
};
这样的设计缺乏合理性,通常来说,修改std里的组件是禁止的。
operator<不仅是实现less的默认方式,它还是程序员希望less做的,上面的问题我们可以建立一个functor class来实现如下:
struct MaxSpeedCompare:
	public binary_function<Widget, Widget, bool> {
	bool operator()(const Widget& lhs, const Widget& rhs) const
	{
		return lhs.maxSpeed() < rhs.maxSpeed();
	}
};
multiset<Widget, MaxSpeedCompare> widgets;
而multiset<Widget>是按默认less<Widget>来排序的,也就是使用operator<。

你可能感兴趣的:(Effective STL笔记(2)-Functors, Functor Classes, Functions, etc.)