您的位置:首页 > 编程语言 > C语言/C++

我的C++实践(2):模板代码的测试

2016-07-29 00:00 106 查看
C++中的模板产生类代码的过程非常复杂,而模板所表示的泛型代码还要依赖于使用模板的客户端。比如模板本身的位置、使用模板的位置、定义模板实参的位置等都会对模板的实例化产生影响。因此,模板代码的测试和调试都比较难。跟踪程序(tracer)就是一种可以跟踪模板实际调用步骤的程序,它通常是一个用户自定义的类,只定义了满足模板测试的一些功能。类的每个操作中都有一个针对该操作的跟踪(比如递增一个计数变量,以记录该操作的调用次数)。用这个类作为模板的实参来使用模板,就可以测试模板的功能。
跟踪程序是一种比较简单和行之有效的技术,它虽不能测试出模板代码中的所有错误,但可以在开发周期的早期检测出模板定义中的很多问题,这样就可以减轻后期调试的负担。当我们用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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: