当前位置: 首页 > news >正文

跟我学c++中级篇——模板的调试

一、调试

一个开发者要想写好程序,最重要的就是要学会调试。有的开发者会说,调试还不简单么,有IDE,不就跳进跳出单步快速等等。即使使用GDB等命令方式,也基本是这种情况。比较麻烦的可能是对多线程的调试,如果存在数据同步或者数据是从IO过来,会导致调试的结果与实际并不相符。但这都不是克服不了的困难。
如果再叠加上内存数据查看,指针数据分配等,就更上一层。还有写一些日志、函数和代码的名称、行数的辅助代码等等,那一般的问题都可以平趟了。如果想查看的更底层一些可以看汇编指令,可以看中间代码(宏),这样就更容易找到问题的原因。
其实调试的手段和方法还有很多,辅助的工具和库也有很多,比如前面提到的gprof等,配合着一起,就能起到更好的调试和解决问题的方式。
调试其实跟经验关系更多一些,而和编程的技术并不严格成正比,但不可不否认的是,编程的技术水平越高,解决问题的能力相对来说越强。但调试解决问题更多的可能是与实际遇到的情况越多就越可能更早的发现并定位进而解决问题,也就是说,经验和技术比,在此处经验要占上风。但这并不是绝对的,有的时候恰恰相反。经验越少,反而想得少,更能直接定位问题,当然这是少数情况。
整体上来看,调试能力是一个开发者的综合能力,它虽然不太能体现开发者的编程能力,但往往可以体现一个开发者的功力(整体的技术实力)。因为很少有人去看你的代码运行多流畅,但出现问题后,如果你能第一时间定位并解决这个问题,那么其它人会认为你是一个大牛。
另外多说一句,定位和解决问题是两回事,在解决问题时,不光光靠经验,更多的时候儿需要理论知识,比如调试多线程,就必须考虑线程调度的不可预知行为。特别是在调试一些算法时,必须了解这些算法。这些都是解决问题的重要理论基础。

二、模板的调试

其实很早就想讲一下模板的调试,记得在刚刚接触模板时,一看到模板报出的一大堆错误,第一反应就是懵了,啥也不明白了。其实问题本身并不复杂,编译器也报给了相关的问题所在,但都淹没在了纷杂的错误中,别说对于新手,就是对于老手,同样也感到很别扭,这也是在新的c++标准中不断的演进,特别是使用概念Conceps代替SFINAE一个重要原因(但在某些情况下SFINAE仍然有存在的意义)。
所以模板的调试过程必然是比普通编程要复杂的,即使从编译器、标准(含库)和开发者三个方向同时努力,它的复杂性仍然是无法和普通编程相比的。这也是很多学习c++模板编程的人从进入到劝退的过程的重要原因。不怕出错,就怕不好找到错误,更何况,模板报得错误有的时候儿非常的奇怪,完全不知所以。
所以经常看到一些开发者如果实在调试不过去,就先写一个普通的程序,调试通过后再替换成模板的开发情况。能解决问题就好,不要强求哪个更优秀,但为了节省时间,还是要有一套调试模板的方法。
泛型编程的目的,就是抽象代码,覆盖一般,支持特例。那调试的目的就很明显了,就是实现编程的目的。所以模板调试就是通过调试确保抽象出来的模板参数可以正常的实现功能并保持对特例的支持。如果不能实现上述功能,则修改并实现它。
模板编译比较普遍的解决问题的方式有以下几种:
1、模板的类型检查
在模板编程中,模板参数不匹配或者编译器确定的匹配侧和想象不同是一种最经典的问题。就是写了几个模板函数和非模板函数,开发者想的和编译器实际的编译匹配不一致。另外就是一些类与参数不匹配,比如STL里的List,如果插入一个自定义类,可能需要一个==的运算符重载支持。就是这些基础的错误,往往是模板中最常见的一种错误。
这就需要进行一些基础的类型检查,比如这个类不支持某个函数或者必须是整形等等,这就是SFINAE和Concepts所要解决的问题。
当然,也可以使用一些断言来做一些处理。
2、设计调试类
在一些复杂的场景下,可以自己设计一个实现类来专门对模板类进行调试。比如一些常见的算法类,就可以实现一个,实例化到模板类中,一般常见的问题也就可以暴露出来。这是一种比较好的方式,但对开发者的要求比较高。
3、设计一个控制类
在一些需要进行复杂的数据变换时,可以写一个辅助类,来不断跟踪模板实例化过程和调用过程的关键值的显示。能够更清晰的定位问题所在,并有针对性的将其解决。
4、使用工具或命令等查看函数或者类
这个一般说来适用于链接错误,链接错误比较常见的是库的版本不匹配,接口不一致等等。这些都需要检查。
5、整体分析
这个就是考验开发者个人的能力了,综合动用上述的各种方式及能想到所有的方法来定位并解决模板的问题。

在上面的各种方法前,有一个需要重点提示的地方,不要犯低级错误,诸如名字有一字之差(甚至大小写不同,这也是强调用IDE来调试的一个原因),没有实现函数或者类等等。这些低级错误,往往让人备受打击。

三、例程调试过程

下面给出一个简单的控制跟踪的一个例程:

#include <iostream>
class SortTracer {
private:
int value; // integer value to be sorted
int generation; // generation of this tracer
inline static long n_created = 0; // number of constructor calls
inline static long n_destroyed = 0; // number of destructor calls
inline static long n_assigned = 0; // number of assignments
inline static long n_compared = 0; // number of comparisons
inline static long n_max_live = 0; // maximum of existing objects
// recompute maximum of existing objects
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:
// constructor
SortTracer (int v = 0) : value(v), generation(1) {
++n_created;
update_max_live();
std::cerr << "SortTracer #" << n_created
<< ", created generation " << generation
<< " (total: " << n_created - n_destroyed
<< ")\n";
}
// copy constructor
SortTracer (SortTracer const& b)
: value(b.value), generation(b.generation+1) {
++n_created;
update_max_live();
std::cerr << "SortTracer #" << n_created
<< ", copied as generation " << generation
<< " (total: " << n_created - n_destroyed
<< ")\n";
}
// destructor
~SortTracer() {
++n_destroyed;
update_max_live();
std::cerr << "SortTracer generation " << generation
<< " destroyed (total: "
<< n_created - n_destroyed << ")\n";
}
// assignment
SortTracer& operator= (SortTracer const& b) {
++n_assigned;
std::cerr << "SortTracer assignment #" << n_assigned
<< " (generation " << generation
<< " = " << b.generation
<< ")\n";
value = b.value;
return *this;
}
// comparison
friend bool operator < (SortTracer const& a,660 Chapter 28: Debugging Templates
SortTracer const& b) {
++n_compared;
std::cerr << "SortTracer comparison #" << n_compared
<< " (generation " << a.generation
<< " < " << b.generation
<< ")\n";
return a.value < b.value;
}
int val() const {
return value;
}
};#include <iostream>
#include <algorithm>
#include "tracer.hpp"
int main()
{
// prepare sample input:
SortTracer input[] = { 7, 3, 5, 6, 4, 2, 0, 1, 9, 8 };
// print initial values:
for (int i=0; i<10; ++i) {
std::cerr << input[i].val() << ’ ’;
}
std::cerr << ’\n’;
// remember initial conditions:
long created_at_start = SortTracer::creations();
long max_live_at_start = SortTracer::max_live();
long assigned_at_start = SortTracer::assignments();
long compared_at_start = SortTracer::comparisons();
// execute algorithm:
std::cerr << "---[ Start std::sort() ]--------------------\n";
std::sort<>(&input[0], &input[9]+1);
std::cerr << "---[ End std::sort() ]----------------------\n";
// verify result:
for (int i=0; i<10; ++i) {
std::cerr << input[i].val() << ’ ’;
}
std::cerr << "\n\n";
// final report:
std::cerr << "std::sort() of 10 SortTracer’s"
<< " was performed by:\n "
<< SortTracer::creations() - created_at_start
<< " temporary tracers\n "
<< "up to "
<< SortTracer::max_live()
<< " tracers at the same time ("
<< max_live_at_start << " before)\n "
<< SortTracer::assignments() - assigned_at_start
<< " assignments\n "
<< SortTracer::comparisons() - compared_at_start
<< " comparisons\n\n";
}
  • 上面代码来自《C++ Templates Second Edition》
    通过这个跟踪器可以对STL中的std::sort()算法进行数据追踪,从而在编写时可以得到相关的问题信息。

对模板的调试能使用IDE尽量使用IDE,因为IDE对某些形式的检查还是相当完备的。同时在编译过程中可以提供一些具体的问题的定位和相关的具体的信息。不要小看这些信息,其实大多数的模板编译问题和链接问题都可以从这些提示出找出原因并解决。不是说使用命令不行,是使用命令不好更好的和源码匹配需要来回的切换。有一些牛人把VIM之类的工具弄成IDE差不多,但使用命令的方式,这个其实仍然是使用IDE。不做过多的讨论。

四、总结

在写完这篇文章后,发现调试其实有时间可以整体上的讲一遍,就和抖音教人买高铁票啥的一样,很多人可能觉得这个很LOW,其实,有非常多的人不会或者根本不知道怎么能在些更进一步。从0到1,从外行到初窥门径,对于相当多数人来说,其实真得需要有人来带一下,戳破一下这层窗户纸。
毕竟写文章的目的是让喜欢编程的人用更小的阻力进入编程的这个世界。

相关文章:

  • string类的总结
  • springboot jar包 无法读取静态资源文件
  • py 异步
  • 【2】SM2验签工具和RSA验签工具
  • EasyExcel导入从第几行开始
  • Linux的几个常用基本指令
  • 对象和数据结构
  • 【AI视野·今日Robot 机器人论文速览 第六十二期】Wed, 25 Oct 2023
  • debian 修改镜像源为阿里云【详细步骤】
  • Leetcode 【2342. 数位和相等数对的最大和】
  • 【Spring】AOP进阶-JoinPoint和ProceedingJoinPoint详解
  • 实力进阶,教你使用thinkphp6开发一款商城系统
  • 电力感知边缘计算网关产品设计方案-网关软件架构
  • 金融业务系统: Service Mesh用于安全微服务集成
  • 好用的开源项目地址
  • .pyc 想到的一些问题
  • ES6 学习笔记(一)let,const和解构赋值
  • iOS编译提示和导航提示
  • JavaScript 无符号位移运算符 三个大于号 的使用方法
  • Mac 鼠须管 Rime 输入法 安装五笔输入法 教程
  • MaxCompute访问TableStore(OTS) 数据
  • Odoo domain写法及运用
  • QQ浏览器x5内核的兼容性问题
  • React Native移动开发实战-3-实现页面间的数据传递
  • Sublime Text 2/3 绑定Eclipse快捷键
  • VirtualBox 安装过程中出现 Running VMs found 错误的解决过程
  • Vue ES6 Jade Scss Webpack Gulp
  • 电商搜索引擎的架构设计和性能优化
  • 关于springcloud Gateway中的限流
  • 极限编程 (Extreme Programming) - 发布计划 (Release Planning)
  • 利用jquery编写加法运算验证码
  • 如何使用Mybatis第三方插件--PageHelper实现分页操作
  • 通过几道题目学习二叉搜索树
  • 与 ConTeXt MkIV 官方文档的接驳
  • 完善智慧办公建设,小熊U租获京东数千万元A+轮融资 ...
  • ​​​​​​​​​​​​​​汽车网络信息安全分析方法论
  • ​VRRP 虚拟路由冗余协议(华为)
  • # C++之functional库用法整理
  • #!/usr/bin/python与#!/usr/bin/env python的区别
  • (2.2w字)前端单元测试之Jest详解篇
  • (4)Elastix图像配准:3D图像
  • (BFS)hdoj2377-Bus Pass
  • (C#)获取字符编码的类
  • (笔试题)合法字符串
  • (二开)Flink 修改源码拓展 SQL 语法
  • (附源码)springboot工单管理系统 毕业设计 964158
  • (附源码)流浪动物保护平台的设计与实现 毕业设计 161154
  • (机器学习-深度学习快速入门)第三章机器学习-第二节:机器学习模型之线性回归
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理第3章 信息系统治理(一)
  • (算法)前K大的和
  • (原)Matlab的svmtrain和svmclassify
  • (转)Linux NTP配置详解 (Network Time Protocol)
  • .“空心村”成因分析及解决对策122344
  • .htaccess 强制https 单独排除某个目录
  • .net core 3.0 linux,.NET Core 3.0 的新增功能