昨天看《C++设计新思维》,看到C++有个很有趣但是很少有人用的新特性,局部类。
就是可以在函数里面定义一个类,这个类在函数外面无法访问到。
这样,配合模板就可以在常用的循环里面很方便的做出for_each循环的效果。
因为如果使用自己定义的数据结构,要使用for_each循环就得自己定义迭代子,而且在迭代器模式中,尽管有迭代子的帮助,共有的循环控制代码还是重复了。
比如:
iterator it = vector::begin();
while(it != vector::end())
{
//do something
it = it->next;
}
在这种模式下,对容器的遍历被抽象为迭代子的步进,但是如果需要多个处理,while循环还是要写很多次。
现有的一个方法是使用std::for_each模板,通过
std::for_each(vector::begin(), vector::end(), DoSomethingClass());
这样需要建立一个DoSomethingClass的类。
可惜C++不像Java和C#一样有匿名类的机制,那么一般的解决方法就是在外部建立一个DoSomethingClass的类。然而这样处理就向外界暴露了这个类,使得程序接口更加复杂。
在我的程序中,有这样一个功能:
for(int x = 0 ; x < x_Max; ++x)
{
for(int y = 0 ; y < y_Max; ++y)
{
for(int z = 0 ; z < z_Max; ++z)
{
DoSomeThing();
}
}
ShowProceedBar();
}
Refrush();
在好几个函数中我都有这种循环,唯一的区别就是DoSomeThingA(), DoSomeThingB(),…,这样三重循环的代码就被重复了好几次。重复是代码罪恶的源头。
而对于一个三重循环,要实现stl的iterator接口可能有点复杂。
现在可以用一个漂亮的解决方法。
首先建立一个模板函数:
template<typename Functor>
void DoEach(Functor func)
{
for(int x = 0 ; x < x_Max; ++x)
{
for(int y = 0 ; y < y_Max; ++y)
{
for(int z = 0 ; z < z_Max; ++z)
{
func();
}
}
ShowProceedBar();
}
Refrush();
}
然后根据我的需要,在原来的诸多函数的基础上,比如说:
void DoA( for…for…for (…DoSomeThingA())… );
void DoB( for…for…for (…DoSomeThingB())… );
void DoC( for…for…for (…DoSomeThingC())… );
void DoD( for…for…for (…DoSomeThingD())… );
在DoX()函数里面建立局部类,把原来的操作代码封装到局部类的operator()的定义里面去。因为局部类只在函数内部有效,所以不用担心名字冲突的问题。像这样:
void DoA()
{
class Func
{
public:
void operator()()
{
DoSomeThingA();
}
};
DoEach(Func());
}
void DoB()
{
class Func
{
public:
void operator()()
{
DoSomeThingB();
}
};
DoEach(Func());
}
因为class定义是在编译时处理,所以DoX在运行时实际的代码只有
void DoA()
{
DoEach(Func());
}
void DoB()
{
DoEach(Func());
}
相当于在外部定义了DoX_Functor。但是,得益于局部类的名称局部性控制,使得对与调用DoX函数的外部代码,DoX_Functor完全透明。
新的代码比原来的代码优雅,因为邪恶的重复循环代码只有一处。另外,对于DoX系列函数的调用者,什么都没有改变。可以认为我只是在函数内部做了调整,以达到减少重复的目的。实际上,在我的程序里,DoEach模板是私有成员模板函数,类的接口没有变化。
———————————————————————————–
刚刚在修改我的程序的时候发现了更酷更漂亮的做法,那就是搭配使用宏,来简化一些琐碎的工作。
#define For__Each(code); class Func \
{ \
public: \
void operator()() \
{ \
code \
} \
}; \
DoEach(Func());
然后在DoA里面,只要写
void DoA()
{
For__Each(
DoSomeThingA();
);
}
就可以了。