基于显示管理重载集的C++静态分派研究
2015-10-20唐新国周天宏
唐新国 周天宏
摘要在C++系统中有静态分派编程技术和动态分派编程技术,将一些执行期的分派提前至编译期,可以减少了编译后的代码长度也提高了程序总体运行速度.分析了引入静态分派技术的必要性以及常用的静态分派技术,提出了通过使用Boost库中的enable_if模板族来非侵入地(nonintrusively)显示管理模板函数的重载集的方式来实现C++中静态分派编程技术,不但提出了一种新的静态分派思路,更为重要的是通过这种方式可以根据模板函数的返回值来重载模板函数.
关键词静态分派动态分派模板元编程模板特化
中图分类号TP311文献标识码A文章编号10002537(2015)05007006
A Static Dispatching Technique in C++ with
Explicitly Managing the Overload Set
TANG Xinguo1*, ZHOU Tianhong2
(1.Information Technology School, Hubei Polytechnic Institute, Xiaogan 432100, China;
2.Department of Information Engineering, Wuhan Business University, Wuhan 430056, China)
AbstractIn C++, there exists static dispatching technique and dynamic dispatching technique. Moving some dispatching at run time up to compiling time can reduce length of complied code and improve the overall running speed. The necessity of introducing static dispatching technique and those used commonly was analyzed, and then a way to realize static dispatching technique was presented by using enable_if template group in Boost library nonintrusively explicitly to manage the overloaded set of template functions. More importantly, in this way, one could overload template functions by their return values.
Key wordsstatic dispatching; dynamic dispatching; metaprogramming; template specialization
模板元编程(Metaprogramming)指的是高阶编程[1],它运行在编译期.作为一种高阶C++编程技术,C++强大的模板机制赋予了模板在编译期的运算能力,模板元编程突出了编译期在整个程序构建和运行过程中的地位,努力将计算从运行期提前至编译期,它既能有效地防止程序错误被传播到运行期,又能够实现以静态代码控制动态代码的目标,使计算尽可能完成于编译期的同时也提高了最终程序的运行性能.
MPL[1](MetaProgramming Library)是由David Abrahams和Aleksey Gurtovoy为方便模板元编程而开发的库,2003年被Boost吸纳为其中的一员,此后又历经一些重大修改,目前已经相当完善.MPL的出现是C++模板元编程发展中的一大创举,它提供了一个通用、高层次的编程框架,包括了序列、迭代器、算法、元函数等组件,具有高度的可重用性,提高了模板元编程的效率,使模板元编程的应用范围得到相当的扩展.
C++模板元编程诞生于十多年前,最初的研究方向是编译期数值计算,后来的实践发展证明,此项技术在类型计算领域也可以释放出巨大能量.现在模板元编程主要用于:数据计算、解开循环、类型处理和自动代码生成.
模板元编程技术有两个强大的优势.首先它使得用其他方法很难或不可能的事情变得容易.第二因为 template metaprograms(模板元程序)在 C++ 编译期间执行,它们能将工作从运行时转移到编译时.一个结果就是通常在运行时才能被察觉的错误能够在编译期间被发现.另一个结果是 C++ 程序使得 TMP 的使用在以下每一个方面都能更有效率:更小的可执行代码,更短的运行时间,更少的内存需求.
静态分派技术就是通过一些基于编译期计算出来的结果来选择不同的运行期行为或接口的程序设计方式,该技术能将一些执行期的分派提前至编译期,从而减少了编译后的代码长度,也提高了程序总体运行速度.在C++的著名Loki库中是通过一种将“数值转换成型别”(Int2Type)[2]的技术来实现的.当然在C++中实现静态分派技术的方法很多,如:模板函数的重载、类模板特化等等,本文是通过使用Boost库中的enable_if模板族来非侵入地(nonintrusively)显示管理模板函数的重载集的方式来实现C++中静态分派技术,不但提出了一种新的静态分派思路,更为重要的是通过这种方式可以根据模板函数的返回值来重载模板函数.
湖南师范大学自然科学学报第38卷第5期唐新国等:基于显示管理重载集的C++静态分派研究1C++的静态分派
1.1C++的静态分派的介绍
在C++中可以通过一些基于编译期计算出来的结果来选择不同的运行期行为或接口的程序设计方式称为静态分派(static dispatching).事实上在C++程序设计中经常使用的是执行期进行分派(dispatching),执行期进行分派通常是使用ifelse或switch语句来实现的.大部分情况下其执行期的成本是微不足道的,然而有时还是无法常常这么做,因为ifelse或switch语句要求每一个语句分支都要能够得到编译成功,即使该条件在编译期就知道了.
例如设计一个泛型容器NiftyContainer,它将元素类型参数化[2]:
template〈class T〉
class NiftyContainer{};
由于NiftyContainer是泛型容器,那么它即可以包括类型为T的对象引用,也可以包括指向类型为T的对象指针.如果要复制泛型容器NiftyContainer中的某个元素,可以调用其copy构造函数(针对nonpolymorphic类型)或调用其虚函数Clone()(针对polymorphic类型).具体设计如下:
template〈class T,bool isPolymorphic〉
class NiftyContainer{
…
void DoSomething(){
T*pSomeObj=…;
if(isPolymorphic){
T* pNewObj=pSomeObj〉Clone();
…polymorphic algorithm…(多态算法)
}else{
T* pNewObj=new T(*pSomeObj);//调用copy构造函数
…nonpolymorphic algorithm…(非多态算法)
}
}
};
上述算法表面上看没有问题,但实际上任何一款C++编译器都不会使之侥幸编译通过.因为编译器会编译每一个语句分支,如果多态算法使用pSomeObj〉Clone(),那么对任何一个没有定义成员函数Clone()的类型,编译器都会停留在语句pSomeObj〉Clone()处告之编译通不过.当然对nonpolymorphic类型也有可能编译失败,因为有些类型会将copy构造函数置于private区域.
1.2C++的静态分派的设计方式
在上面的执行期分派算法中,如果可以让编译器不编译那个不可能被执行的代码段为好,例如可以将运行期测试换成相应的编译期测试,C++中静态分派的设计方式通常是[3]:
template 〈class T〉
void f(T x){
if(boost::is_class〈T〉::value){
…implementation 1…
}else{
…implementation 2…
}
}
由于测试条件boost::is_class〈T〉::value可以完全地在编译期决定,所以很多C++编译器会对测试条件boost::is_class〈T〉::value进行优化,并仅为选择了的if语句分支生成代码.这种方式简单清晰,只要它能工作,其概念上的开销就非常小甚至没有,实际上这种静态分派的设计方法也并非普遍适应.
1.3C++静态分派的不足
考虑上面的函数模板被实现为下面的样子时会发生什么[4]:
template 〈class T〉
void f(T x){
if(boost::is_class〈T〉::value){
std::cout〈〈x::value;//处理整型常量外覆器
}else{
std::cout〈〈x; //处理非整型常量外覆器
}
}
这里的意图是使函数模板f能够输出一个整数类型(如int)的值或者一个整型常量外覆器(如long_〈5〉)的值.然而函数调用f(42)就会得到一个编译错误,问题仍然与执行期进行分派一样,在于整个函数都需要进行类型检查,包括if语句的全部分支,但我们无法访问整数类型int的根本不存在的::value成员,这同样也出现了运行期分派的缺点.
采用模板函数的重载[11]、类模板特化[12]等方法可解决上述问题,关键是将每一个分支单独写成一个独立的代码段.采用显示管理重载集的方法来解决C++的静态分派问题.
2基于显式管理重载集对C++静态分派设计方式的改进
2.1显示管理重载集的原理
在C++中,常常使用CRTP[13](奇特的递归模板模式)来识别管理重载集,但有时CRTP对于限制一般化的函数模板实参的范围是很不够的.比如我们可能希望通过函数模板操作内建类型(它们没有基类)或者操作已有的第三方类型(不能修改它们).这时就可以使用Boost库中的enable_if模板族[14]来非侵入地(nonintrusively)显示管理模板函数的重载集,在编译期决定一个参数类型的适当性.
Boost库中的enable_if模板族的工作原理为:
template〈bool,class T=void〉
struct enable_if_c
{
typedef T type;
};
template〈class T〉
struct enable_if_c〈false,T〉
{};
template〈class Cond,class T=void〉
struct enable_if
:enable_if_c〈Cond::value,T〉
{};
注意:当C的值为false时,enable_if_c〈C,T〉::type不存在,根据C++标准的重载决议规则,当一个函数模板的实参推导失败时,它对被考虑准备调用的候选函数集没有贡献,并且不会导致一个错误.这个原则已经被David Vandevoorde和Nicolai Josuttis赋予“替换失败并非错误(Substitution Failure Not An Error,SFINAE)”的名号[15].
2.2C++静态分派设计方式的改进
重载集有时也能进行静态分派,如果能在编译期就能决定一个参数类型的适当性,则可用Boost的enable_if模板非侵入地(nonintrusively)显式管理重载集,从而达到静态分派的目的.用C++的显示管理重载集方式来实现静态分派问题.首先对模板函数
template 〈class T〉
void f(T x){
if(boost::is_class〈T〉::value){
std::cout〈〈x::value;//处理整型常量外覆器
}else{
std::cout〈〈x; //处理非整型常量外覆器
}
}
进行改进.重新设计后的代码为:
template〈class T〉
typename boost::enable_if〈
typename boost::is_class〈T〉::type
〉::type
print(T x){
std::cout〈〈T::value〈〈std::endl;
}
template〈class T〉
typename boost::enable_if〈
typename mpl::not_〈boost::is_class〈T〉 〉::type
〉::type
print(T x){
std::cout〈〈x〈〈std::endl;
}
这样print(42)和print(mpl::int_〈56〉())将访问不同的模板函数,只有匹配的那个模板函数才能实例化且不会出现编译错误.
其次应用C++的显示管理重载集方式来重新实现C++标准程序库中的advance泛型算法.advance泛型算法[5]就是一个使用标签分派的极好例子,advance用来将一个迭代器i移动n个位置,对不同类型的迭代器i实现的策略是不同的.如果i是随机迭代器,则可用i+=n来实现.如果i是双向迭代器,可以在运行期决定是否递增或递减迭代器;如果i是前向迭代器,则只能是向前步进多步.通过编译期的标签分派也可能达到静态分派,它的基本思想是从运行期的泛型编程借用而来.一个标签(tag)仅仅是一个空类[6](empty class),其唯一的用途就是在编译期传达信息.那么改进后的代码为:
template〈class Iterator,class Distance〉
typename boost::enable_if〈
typename boost::is_same〈std::input_iterator_tag,//i是前向迭代器
typename std::iterator_traits〈Iterator〉::iterator_category〉::type
〉::type
advance(Iterator& i,Distance n){
while(n--)++i;
}
template〈class Iterator,class Distance〉
typename boost::enable_if〈
typename boost::is_same〈std::bidirectional_iterator_tag,// i是双向迭代器
typename std::iterator_traits〈Iterator〉::iterator_category〉::type
〉::type
advance(Iterator& i,Distance n){
if(n〉=0)
while(n--)++i;
else
while(n++)--i;
}
template〈class Iterator,class Distance〉
typename boost::enable_if〈
typename boost::is_same〈std::random_access_iterator_tag,// i是随机迭代器
typename std::iterator_traits〈Iterator〉::iterator_category〉::type
〉::type
advance(Iterator& i,Distance n){
i+=n;
}
对于::advance(i,4)调用,同样会根据迭代器i的类型来选择一个合适的模板函数,达到迭代器i的递增,其他可能使用一个给定的迭代器所未实现的重载则永远不会被实例化.从以上实例过程来看:通过C++的显示管理重载集方式来实现静态分派问题就是将每一个可能的分支转换成一个重载的模板函数.
2.3应用拓展
利用Boost库中的enable_if模板族来非侵入地(nonintrusively)显示管理模板函数的重载集实现C++的静态分派问题时,同时也是根据模板函数的返回值来实现模板函数重载的过程.
下面的函数模板仅应用于算术类型的迭代器上并将容器中所有元素之和求出,通过元函数[7]boost::iterator_value来获取一个迭代器的value_type.
template〈class Iterator〉
typename boost::enable_if〈
boost::is_arithmetic〈//启用条件
typename boost::iterator_value〈Iterator〉::type
〉
,typename
boost::iterator_value〈Iterator〉::type//返回类型
〉::type
sum(Iterator start,Iterator end){
typename boost::iterator_value〈Iterator〉::type x(0);
for(;start!=end;++start)
x+=*start;
return x;
}
如果启用条件C的::value为true,enable_if〈C,T〉::type将为T,于是sum将返回一个“Iterator的value_type”的对象.否则sum将从重载决议过程中消失[8].
当有模板函数重载发挥作用时,这项技术就变得真正有趣起来.因为上述模板sum已经根据它返回值被限制为(接收)适当的参数,现在可以添加另一个重载,它允许我们计算出vector〈vector〈int〉〉的所有算术元素,以及其他算术类型的嵌套的容器.
template〈class Iterator〉
struct inner_value
:boost::iterator_value〈
typename boost::iterator_value〈Iterator〉::type::iterator
〉{};
template〈class Iterator〉
typename boost::lazy_disable_if〈
boost::is_arithmetic〈//禁用条件
typename boost::iterator_value〈Iterator〉::type
〉
,inner_value〈Iterator〉//结果无函数
〉::type
sum(Iterator start,Iterator end){
typename inner_value〈Iterator〉::type x(0);
for(;start!=end;++start)
x+=sum(start〉begin(),start〉end());
return x;
}
Lazy_disable_if的“disable”指出当条件被满足时,该函数被从重载集中移走了,“lazy”则意味着函数的结果::type是以一个无参元函数调用第二个参数的结果[9].
注意,只有当迭代器的值类型是另一个迭代器时,inner_value〈Iterator〉才能被调用,这时sum(Iterator start,Iterator end)会调用第二个模板函数.当迭代器的值类型是算术类型时,sum(Iterator start,Iterator end)会调用第一个模板函数.对于其他类型的实参,当发生实参推导失败时,那么在重载决议期间它对考虑准备调用的候选函数集没有贡献[10],这时并不会发生错误,这个过程当然也可以看成是根据模板函数的返回值来实现模板函数重载的过程.
3结束语
模板元编程是C++中一种高级编程技术,它处于编译期,静态分派技术是基于编译期计算出来的结果来选择不同的运行期行为或接口的程序设计方式,这种技术能将一些执行期的分派提前至编译期,从而减少了编译后的代码长度,也提高了程序总体运行速度.这里则是通过使用Boost库中的enable_if模板族来非侵入地(nonintrusively)显示管理模板函数的重载集的方式来实现C++中静态分派技术,提出了一种新的静态分派思路,通过这种方式可以根据模板函数的返回值来重载模板函数.
参考文献:
[1]DAVID A.C++模板元编程[M].荣耀,译.北京:机械工业出版社,2010.
[2]ANDREI A.C++设计新思维[M].侯捷,於春景,译.武汉:华中科技大学出版社,2003.
[3]DAVID V, NICOLAI M J. C++ template中文版[M].陈伟柱,译.北京:人民邮电出版社,2004.
[4]HERBERT S.C++完全参考手册[M].4版.北京:清华大学出版社,2007.
[5]王晓宇,钱红兵.基于UML类图和顺序图的C++代码自动生成方法的研究[J].计算机应用与软件, 2013,30(1):190195.
[6]周毅,顾进广,张晓龙,等.一种面向复合属性的自适应对象模型[J].计算机应用与软件, 2008,25(11):137139.
[7]徐静雯,周继恩,施跃跃,等.软件密集型系统的故障诊断技术研究[J].计算机应用与软件, 2012,29(2):175178.
[8]黄山,陈昱松,王建伟,等.一种基于UML与SDL融合建模的组件系统测试方法[J].计算机应用与软件, 2011,28(7):175177,182.
[9]唐峰,许第洪.SolidWorks与Pro/Engineer之间图形数据交换方式的研究[J].湖南师范大学自然科学学报, 2011,34(1):3742.
[10]刘震,缪力.基于动态调用图的Java程序修改影响分析技术[J].湖南师范大学自然科学学报, 2011,34(6):2630.
[11]PLAUGER P J, ALEXANDER A S.C++ STL中文版[M].王昕,译.北京:中国电力出版社,2002.
[12]JASMIN B, MARK S.C++ GUI Qt 4编程[M].闫锋欣,译.北京:电子工业出版社,2008.
[13]叶至军.C++ STL开发技术导引[M].北京:人民邮电出版社, 2007.
[14]MATTHEW H A. 泛型编程与STL[M].侯捷,译.北京:中国电力出版社,2003.
[15]ANDREW K, BARBARA M. C++沉思录[M].黄晓春,译.北京:人民邮电出版社,2008.
(编辑陈笑梅)