我的C++实践(2):模板代码的测试
2016-07-29 00:00
106 查看
C++中的模板产生类代码的过程非常复杂,而模板所表示的泛型代码还要依赖于使用模板的客户端。比如模板本身的位置、使用模板的位置、定义模板实参的位置等都会对模板的实例化产生影响。因此,模板代码的测试和调试都比较难。跟踪程序(tracer)就是一种可以跟踪模板实际调用步骤的程序,它通常是一个用户自定义的类,只定义了满足模板测试的一些功能。类的每个操作中都有一个针对该操作的跟踪(比如递增一个计数变量,以记录该操作的调用次数)。用这个类作为模板的实参来使用模板,就可以测试模板的功能。
跟踪程序是一种比较简单和行之有效的技术,它虽不能测试出模板代码中的所有错误,但可以在开发周期的早期检测出模板定义中的很多问题,这样就可以减轻后期调试的负担。当我们用C++开发模板代码时,使用跟踪程序是一种比较好的测试技巧。
下面开发一个跟踪程序Tracer,并用它测试排序算法std::sort(这是一个函数模板),这可以确认算法的效率和操作的实际调用步骤。类Tracer是跟踪程序,它可以跟踪算法运行时产生对象拷贝的份数、调用构造函数的次数、调用析构函数的次数、对象赋值的次数、对象比较的次数、现存对象的最大个数等。
用这个跟踪程序来测试排序算法。如下:
运行结果如下:
这里“Tracer creation #1, created as generation 1 (total: 1)“表示第1次创建对象(直接创建),被创建为第generation份拷贝(首次创建时表示第1份拷贝,以后调用复制构造函数时,拷贝份数会增加),total表示现存对象的总个数。
“Tracer creation #11, copied as generation 2 (total: 11)“表示第11次创建对象(通过复制构造函数创建),被复制为第generation份拷贝。
“Tracer comparison #1 (generation 2 < 1)“表示第1次比较,比较左边对象的第2份拷贝是否小于右边对象的第1份拷贝。
“Tracer assignment #1 (generation 1 = 1)“表示第1次赋值:把右边对象的第1份拷贝赋给左边对象的第1份拷贝。
“Tracer destruction #1, generation 2 destroyed (total: 10)“表示第1次销毁对象,当前对象的第2份拷贝被销毁。
从最后的统计报告中可以看出,对10个对象运行std::sort算法进行排序,总共创建了15个临时对象,但在同一时刻最多只存在2个多余的对象(最多有12个,减去要排序的10),这让我们对算法的开销有了基本的把握。运行也说明了我们的tracer完全满足标准sort()算法的要求,例如不需要运算符==和运算符>。从输出的排序结果可以看出基本上没有问题。特别要注意这只是一个大概的判断,它并不能断定算法的内部代码实现的完全正确性。
对tracer增加断言和推理,或者连接到推理引擎(是一个程序,可以记住用来推导出结论的断言和推理)等,就可以扩展成所谓的oracles(测试谕示)。oracles可以动态地验证算法,它不需要完全指定模板实参(oracles本身就是实参),也不需要在程序中指定输入数据(推理引擎会请求用户来输入)。当前使用oracles对算法复杂度的分析也是非常有限的。
tracer是跟踪模板所需要的最小接口。当tracer不产生运行期输出时,我们有时也称之为archetype(原型)。利用archetype我们可以验证模板是否会请求不符合期望的语法约束。对于一个模板的实现,我们一般要为模板库中标记的每个concept都开发一个archetype。
跟踪程序是一种比较简单和行之有效的技术,它虽不能测试出模板代码中的所有错误,但可以在开发周期的早期检测出模板定义中的很多问题,这样就可以减轻后期调试的负担。当我们用C++开发模板代码时,使用跟踪程序是一种比较好的测试技巧。
下面开发一个跟踪程序Tracer,并用它测试排序算法std::sort(这是一个函数模板),这可以确认算法的效率和操作的实际调用步骤。类Tracer是跟踪程序,它可以跟踪算法运行时产生对象拷贝的份数、调用构造函数的次数、调用析构函数的次数、对象赋值的次数、对象比较的次数、现存对象的最大个数等。
//tracer.hpp:跟踪程序,一个我们定义的类 #include <iostream> class Tracer{ private: int value; //要被排序的整数值 int generation; //一个对象拷贝的份数 static long n_created; //调用构造函数的次数 static long n_destroyed; //调用析构函数的次数 static long n_assigned; //赋值的次数 static long n_compared; //比较的次数 static long n_max_live; //现存对象的最大个数 //重新计算现存对象的最大个数 static void update_max_live(){ if(n_created-n_destroyed>n_max_live){ n_max_live=n_created-n_destroyed; } } public: static long creations(){ return n_created; } static long destructions(){ return n_destroyed; } static long assignments(){ return n_assigned; } static long comparisons(){ return n_compared; } static long max_live(){ return n_max_live; } public: //构造函数 Tracer(int v=0):value(v),generation(1){ //首次创建:产生对象的第一份拷贝 ++n_created; update_max_live(); //表示第n_created次创建对象(直接创建),被创建为第generation份拷贝 //(首次创建时表示第1份拷贝),total表示现存对象的总个数 std::cerr<<"Tracer creation #"<<n_created <<", created as generation "<<generation <<" (total: "<<n_created-n_destroyed<<")/n"; } //拷贝构造函数 Tracer(Tracer const& b):value(b.value),generation(b.generation+1){ //给对象b增加一份拷贝 ++n_created; update_max_live(); //表示第n_created次创建对象(复制创建),被复制为第generation份拷贝 std::cerr<<"Tracer creation #"<<n_created <<", copied as generation "<<generation <<" (total: "<<n_created-n_destroyed<<")/n"; } //析构函数 ~Tracer(){ ++n_destroyed; update_max_live(); //表示第n_destroyed次销毁对象,对象当前的第generation份拷贝被销毁 std::cerr<<"Tracer destruction #"<<n_destroyed<<", generation "<<generation <<" destroyed (total: "<<n_created-n_destroyed<<")/n"; } //赋值运算符 Tracer& operator=(Tracer const& b){ if(this==&b) return *this; ++n_assigned; //表示第n_assigned次赋值:把右边对象的第b.generation份拷贝赋给左边对象的第generation份拷贝 std::cerr<<"Tracer assignment #"<<n_assigned <<" (generation "<<generation<<" = "<<b.generation<<")/n"; value=b.value; return *this; } //比较运算符 friend bool operator<(Tracer const& a,Tracer const& b){ ++n_compared; //表示第n_compared次比较:把左边对象的第a.generation份拷贝与右边对象 //的第b.generation份拷贝作<比较 std::cerr<<"Tracer comparison #"<<n_compared <<" (generation "<<a.generation<<" < "<<b.generation<<")/n"; return a.value<b.value; } int val() const { return value; } }; long Tracer::n_created=0; long Tracer::n_destroyed=0; long Tracer::n_max_live=0; long Tracer::n_assigned=0; long Tracer::n_compared=0;
用这个跟踪程序来测试排序算法。如下:
//tracertest.cpp:用跟踪程序来测试std::sort这个函数模板的功能 #include <iostream> #include <algorithm> #include "tracer.hpp" int main(){ //准备输入的例子:构造函数会对给定的整数值执行隐式转换 Tracer input[]={7,3,5,6,4,2,0,1,9,8}; //输出初始值 for(int i=0;i<10;++i) std::cerr<<input[i].val()<<' '; std::cerr<<std::endl; //存取初始状态 long created_at_start=Tracer::creations(); long max_live_at_start=Tracer::max_live(); long assigned_at_start=Tracer::assignments(); long compared_at_start=Tracer::comparisons(); //执行算法 std::cerr<<"/n/n---[ Start std::sort() ]--------------------------------------------------/n/n"; std::sort<>(&input[0],&input[9]+1); std::cerr<<"/n/n---[ End std::sort() ]----------------------------------------------------/n/n"; //确认结果 for(int i=0;i<10;++i) std::cerr<<input[i].val()<<' '; std::cerr<<"/n/n"; //最后的输出报告 std::cerr<<"Statistics: "<<std::endl; std::cerr<<"std::sort() of 10 Tracer's was performed by:/n" <<Tracer::creations()-created_at_start<<" temporary tracers/n"<<"up to " <<Tracer::max_live()<<" tracers at the same time (" <<max_live_at_start<<" before)/n" <<Tracer::assignments()-assigned_at_start<<" assignments/n" <<Tracer::comparisons()-compared_at_start<<" comparsions/n/n"; }
运行结果如下:
zhouhuansheng@laptop-zhou:~/zhou/code/cpp_templates/basics$ g++ -Wall tracertest.cpp -o tracertest zhouhuansheng@laptop-zhou:~/zhou/code/cpp_templates/basics$ ./tracertest Tracer creation #1, created as generation 1 (total: 1) Tracer creation #2, created as generation 1 (total: 2) Tracer creation #3, created as generation 1 (total: 3) Tracer creation #4, created as generation 1 (total: 4) Tracer creation #5, created as generation 1 (total: 5) Tracer creation #6, created as generation 1 (total: 6) Tracer creation #7, created as generation 1 (total: 7) Tracer creation #8, created as generation 1 (total: 8) Tracer creation #9, created as generation 1 (total: 9) Tracer creation #10, created as generation 1 (total: 10) 7 3 5 6 4 2 0 1 9 8 ---[ Start std::sort() ]-------------------------------------------------- Tracer creation #11, copied as generation 2 (total: 11) Tracer comparison #1 (generation 2 < 1) Tracer assignment #1 (generation 1 = 1) Tracer assignment #2 (generation 1 = 2) Tracer destruction #1, generation 2 destroyed (total: 10) Tracer creation #12, copied as generation 2 (total: 11) Tracer comparison #2 (generation 2 < 1) Tracer creation #13, copied as generation 3 (total: 12) Tracer comparison #3 (generation 3 < 1) Tracer assignment #3 (generation 1 = 1) Tracer comparison #4 (generation 3 < 1) Tracer assignment #4 (generation 1 = 3) Tracer destruction #2, generation 3 destroyed (total: 11) Tracer destruction #3, generation 2 destroyed (total: 10) Tracer creation #14, copied as generation 2 (total: 11) Tracer comparison #5 (generation 2 < 1) Tracer creation #15, copied as generation 3 (total: 12) Tracer comparison #6 (generation 3 < 1) Tracer assignment #5 (generation 1 = 1) Tracer comparison #7 (generation 3 < 1) Tracer assignment #6 (generation 1 = 3) Tracer destruction #4, generation 3 destroyed (total: 11) Tracer destruction #5, generation 2 destroyed (total: 10) Tracer creation #16, copied as generation 2 (total: 11) Tracer comparison #8 (generation 2 < 1) Tracer creation #17, copied as generation 3 (total: 12) Tracer comparison #9 (generation 3 < 1) Tracer assignment #7 (generation 1 = 1) Tracer comparison #10 (generation 3 < 1) Tracer assignment #8 (generation 1 = 1) Tracer comparison #11 (generation 3 < 1) Tracer assignment #9 (generation 1 = 1) Tracer comparison #12 (generation 3 < 1) Tracer assignment #10 (generation 1 = 3) Tracer destruction #6, generation 3 destroyed (total: 11) Tracer destruction #7, generation 2 destroyed (total: 10) Tracer creation #18, copied as generation 2 (total: 11) Tracer comparison #13 (generation 2 < 1) Tracer assignment #11 (generation 1 = 1) Tracer assignment #12 (generation 1 = 1) Tracer assignment #13 (generation 1 = 1) Tracer assignment #14 (generation 1 = 1) Tracer assignment #15 (generation 1 = 1) Tracer assignment #16 (generation 1 = 2) Tracer destruction #8, generation 2 destroyed (total: 10) Tracer creation #19, copied as generation 2 (total: 11) Tracer comparison #14 (generation 2 < 1) Tracer assignment #17 (generation 1 = 1) Tracer assignment #18 (generation 1 = 1) Tracer assignment #19 (generation 1 = 1) Tracer assignment #20 (generation 1 = 1) Tracer assignment #21 (generation 1 = 1) Tracer assignment #22 (generation 1 = 1) Tracer assignment #23 (generation 1 = 2) Tracer destruction #9, generation 2 destroyed (total: 10) Tracer creation #20, copied as generation 2 (total: 11) Tracer comparison #15 (generation 2 < 1) Tracer creation #21, copied as generation 3 (total: 12) Tracer comparison #16 (generation 3 < 1) Tracer assignment #24 (generation 1 = 1) Tracer comparison #17 (generation 3 < 1) Tracer assignment #25 (generation 1 = 1) Tracer comparison #18 (generation 3 < 1) Tracer assignment #26 (generation 1 = 1) Tracer comparison #19 (generation 3 < 1) Tracer assignment #27 (generation 1 = 1) Tracer comparison #20 (generation 3 < 1) Tracer assignment #28 (generation 1 = 1) Tracer comparison #21 (generation 3 < 1) Tracer assignment #29 (generation 1 = 1) Tracer comparison #22 (generation 3 < 1) Tracer assignment #30 (generation 1 = 3) Tracer destruction #10, generation 3 destroyed (total: 11) Tracer destruction #11, generation 2 destroyed (total: 10) Tracer creation #22, copied as generation 2 (total: 11) Tracer comparison #23 (generation 2 < 1) Tracer creation #23, copied as generation 3 (total: 12) Tracer comparison #24 (generation 3 < 1) Tracer assignment #31 (generation 1 = 3) Tracer destruction #12, generation 3 destroyed (total: 11) Tracer destruction #13, generation 2 destroyed (total: 10) Tracer creation #24, copied as generation 2 (total: 11) Tracer comparison #25 (generation 2 < 1) Tracer creation #25, copied as generation 3 (total: 12) Tracer comparison #26 (generation 3 < 1) Tracer assignment #32 (generation 1 = 1) Tracer comparison #27 (generation 3 < 1) Tracer assignment #33 (generation 1 = 3) Tracer destruction #14, generation 3 destroyed (total: 11) Tracer destruction #15, generation 2 destroyed (total: 10) ---[ End std::sort() ]---------------------------------------------------- 0 1 2 3 4 5 6 7 8 9 Statistics: std::sort() of 10 Tracer's was performed by: 15 temporary tracers up to 12 tracers at the same time (10 before) 33 assignments 27 comparsions Tracer destruction #16, generation 1 destroyed (total: 9) Tracer destruction #17, generation 1 destroyed (total: 8) Tracer destruction #18, generation 1 destroyed (total: 7) Tracer destruction #19, generation 1 destroyed (total: 6) Tracer destruction #20, generation 1 destroyed (total: 5) Tracer destruction #21, generation 1 destroyed (total: 4) Tracer destruction #22, generation 1 destroyed (total: 3) Tracer destruction #23, generation 1 destroyed (total: 2) Tracer destruction #24, generation 1 destroyed (total: 1)
这里“Tracer creation #1, created as generation 1 (total: 1)“表示第1次创建对象(直接创建),被创建为第generation份拷贝(首次创建时表示第1份拷贝,以后调用复制构造函数时,拷贝份数会增加),total表示现存对象的总个数。
“Tracer creation #11, copied as generation 2 (total: 11)“表示第11次创建对象(通过复制构造函数创建),被复制为第generation份拷贝。
“Tracer comparison #1 (generation 2 < 1)“表示第1次比较,比较左边对象的第2份拷贝是否小于右边对象的第1份拷贝。
“Tracer assignment #1 (generation 1 = 1)“表示第1次赋值:把右边对象的第1份拷贝赋给左边对象的第1份拷贝。
“Tracer destruction #1, generation 2 destroyed (total: 10)“表示第1次销毁对象,当前对象的第2份拷贝被销毁。
从最后的统计报告中可以看出,对10个对象运行std::sort算法进行排序,总共创建了15个临时对象,但在同一时刻最多只存在2个多余的对象(最多有12个,减去要排序的10),这让我们对算法的开销有了基本的把握。运行也说明了我们的tracer完全满足标准sort()算法的要求,例如不需要运算符==和运算符>。从输出的排序结果可以看出基本上没有问题。特别要注意这只是一个大概的判断,它并不能断定算法的内部代码实现的完全正确性。
对tracer增加断言和推理,或者连接到推理引擎(是一个程序,可以记住用来推导出结论的断言和推理)等,就可以扩展成所谓的oracles(测试谕示)。oracles可以动态地验证算法,它不需要完全指定模板实参(oracles本身就是实参),也不需要在程序中指定输入数据(推理引擎会请求用户来输入)。当前使用oracles对算法复杂度的分析也是非常有限的。
tracer是跟踪模板所需要的最小接口。当tracer不产生运行期输出时,我们有时也称之为archetype(原型)。利用archetype我们可以验证模板是否会请求不符合期望的语法约束。对于一个模板的实现,我们一般要为模板库中标记的每个concept都开发一个archetype。
相关文章推荐
- 我的C++实践(4):trait与policy模板技术
- CPP全面总结(涵盖C++11标准)
- Cpp多重继承会产生的问题
- 我的C++实践(6):模板与继承相结合的威力
- 我的C++实践(13):多态化的构造函数和非成员函数
- 我的C++实践(15):判断对象是在堆上还是在栈上
- C++语言的黑客行为
- 我的C++实践(12):函数指针与仿函数
- 我的C++实践(16):引用计数实现
- 我的C++实践(14):限制类对象的个数
- 我的C++实践(10):智能指针
- C++ Primer学习系列(4):关联容器/泛型算法/类
- C++ Primer学习系列(3):函数/标准IO库/顺序容器
- 我的C++实践(7):模板元编程实战
- SQLite剖析(3):C/C++接口介绍
- 我的C++实践(14):限制类对象的个数
- 我的C++实践(3):用多态机制来做设计
- 我的C++实践(10):智能指针
- 我的C++实践(18):多态的双重分派实现
- C++ Primer学习系列(4):关联容器/泛型算法/类