一、问题
在实际开发中遇到了两个小问题,一个是文件流的读写中的长度和结尾判断;另外一个是C++11库std::chrono::duration的数据类型的问题。这两个问题导致了两个结果:
1、流结尾判断不准确,多读一帧导致长度判断恒为正确,文件不加载
2、对标准库的时间库认知不统一,导致单位不同出现错判
这样说可能不好理解,在下面分别进行代码说明。
二、文件问题解决
先看一下文件处理的原始代码:
c
#include <fstream>
#include <iostream>
int main() {
std::ifstream f(filename);
if (!f.good()) {
return 0;
}
char buf[1024];
static std::streamsize count = 1024;
while (!f.eof()) {
size_t c = f.getline(buf, count).gcount();//可正常比较
// size_t c = metaFileStream.gcount();//可正常比较
std::cerr << "----" << f.gcount() << std::endl;
auto dlen = f.gcount();
//下面的判断进入,导致文件错误
if (dlen < 3/*f.gcount()<3*/) {//问题主要在这里,原始的代码是注释掉的部分,但此处二者结果一致
std::cerr << "dlen is:" << dlen << std::endl;
}
if (static_cast<size_t>(f.gcount()) < 3) {//可正常比较
std::cerr << "static_cast count compare!" << std::endl;
return 0;
}
}
return 1;
}
这段代码本来是没有问题的,后面有一个长度判断的,但后来更改增加了不能执行的部分后,程序基本就无法运行了。因为只要有运行就需要加载文件,而在此处则无法运行下去,直接报文件读取的数据太少,也就是上面注释里的那段无法执行的代码返回的。
后来根据代码来要看,怀疑是不是getline函数对gcount函数有类似清除的机制,但查看官方文档后发现没有这个问题,只是提供了对长度的变化的影响,这反而是程序需要的。后来就怀疑到是不是不同类型的比较导致的,毕竟在很早以前就被有符号和无符号的类型整过。查看文档中gcount()返回的数据类型是streamsize,而它的定义是:
c
#define __PTRDIFF_TYPE__ long int
typedef __PTRDIFF_TYPE__ ptrdiff_t;
这应该没有问题的。继续分析,打印发现起始都是正常的,但读到最后会打印一个长度为0的读取缓冲,所以进入了文件错误判断的分支中。忽然想起了,文件流读取最后一帧后是需要再读取一帧才能将f.eof()转为false。也就是说,正常读取完成后,其结果仍然是True,只有继续读取出现错误时,才会置为False。所以会出现一个空读取,导致进入异常判断。知道问题原因后就比较好解决了:
c
#include <fstream>
#include <iostream>
int main() {
std::ifstream f(filename);
if (!f.good()) {
return 0;
}
char buf[1024];
static std::streamsize count = 1024;
while (f.peek()!=EOF) {
size_t c = f.getline(buf, count).gcount();
//正常
if (c < 3) {
std::cerr << "dlen is:" << dlen << std::endl;
}
if (static_cast<size_t>(f.gcount()) < 3) {
std::cerr << "static_cast count compare!" << std::endl;
return 0;
}
}
return 1;
}
不过在最近的开发中遇到过几次定义完全相同的类型,在被反复重新定义后就无法进行转换。比如在某个库里定义了Uint16,而程序中有uint16_t,这两种类型本质都是一种类型,但在实际编译中报错,无法通过编译。这里面的原因很多,不做细的分析,只是提醒开发者要注意这方面的细节。
三、时间问题解决
时间问题的处理主要原因在于对时间问题的掌握还是不系统,没有从根本上明白时间库的定义,先看类似原始代码:
c
int main(){
//求运行时间1
auto t1 = std::chrono::steady_clock::now();
dowork();
auto t2 = std::chrono::steady_clock::now();
auto escape1 = t2 - t1;//这种情况单位是什么
std::cerr << "escape1 is:" << escape1.count() << std::endl;
//求运行时间2
auto t3 = std::chrono::steady_clock::now();
dowork();
auto t4 = std::chrono::steady_clock::now();
std::chrono::duration<double> escape2 = t4 - t3;//这种情况单位是什么
std::cerr << "escape1 is:" << escape2.count() << std::endl;
//如果出现运行结果中的科学计数方法可使用下面的方式打印
std::cerr << std::fixed << std::setprecision(9) << "escape1 is:" << escape2.count() << std::endl;
//求运行时间3
auto start = std::chrono::high_resolution_clock::now();
dowork();
auto end = std::chrono::high_resolution_clock::now();
std::chrono::microseconds escape3 = std::chrono::duration_cast<std::chrono::microseconds>(end - start);//此处单位是微秒
std::cerr << "escape1 is:" << escape3.count() << std::endl;
return 0;
}
运行结果:
cape1 is:2532329
escape1 is:0.00255415 //有可能出现类似escape1 is:3.82e-07的科学计数
escape1 is:2559
有没有发现,上面的打印都没有带单位,这也这次的一个主要问题。特别是std::chrono::duration竟然一时没反应过来,后来查看文档说明才知道,其定义为:
c
template<
class Rep,
class Period = std::ratio<1>
> class duration;
template<
std::intmax_t Num,
std::intmax_t Denom = 1
> class ratio;
其实重点是要明白ratio的定义,duration等价于duration<double,std::ratio<1>>,又等价于duration<double,std::ratio<1,1>>即以秒为基准单位。对照上面的显示,可以知道,三个打印结果的单位是纳秒、秒和微秒。再根据现实生活中时间的定义,ratio<60>(ratio<60,1>)代表一分钟,ratio<6060>(ratio<60 60,1>)代表一小时,ratio<1, 1000>代表一毫秒,ratio<1, 1000000>代表一微秒,ratio<1, 1000000000>代表一纳秒。
所以在标准库中定义了一些常用的时间:
c
typedef duration <Rep, ratio<3600,1>> hours;
typedef duration <Rep, ratio<60,1>> minutes;
typedef duration <Rep, ratio<1,1>> seconds;
typedef duration <Rep, ratio<1,1000>> milliseconds;
typedef duration <Rep, ratio<1,1000000>> microseconds;
typedef duration <Rep, ratio<1,1000000000>> nanoseconds;
在C++20后又出现了周(week)、月(months)和年(years)的单位,而在某些场景下可能会以某些特定的时间量为单位,所以才会有ratio<N,N>的用法。而duration也可以是duration等,其实就是表示数据的类型,浮点可能精度更高一些。
这个问题之所以出现,主要是程序中的时间太小,出现了科学计算法,同时,单位的不统一导致口算时明显数据不正确。以前基本都是统一的时间单位控制,而这块代码因为与库中的代码进行兼容,所以才出现了计算上的差异。std::chrono::duration_cast的机制与分析大略相同。
无它,终究还是基础知识不扎实导致。
提一个问题:"auto t1 = std::chrono::steady_clock::now();"返回的 time_point的Rep类型是什么类型?它从哪里来?或者说std::chrono::steady_clock的默认的Rep是什么类型?
四、总结
万丈高楼平地起,基础的重要性已经反复说得都麻了。但不得不说,往往都明白基础的重要性,可其实并未真正时刻挂在心上。上面的例子就说明了学习态度中的一些瑕疵,与诸君共同努力改正吧。