为什么C++程序跑不过C程序?
举个例子,让你按行读取文本文件,你会怎么写?使用全局函数std::getline()吗?
[code lang="cpp" title="代码1"]
void read_line_by_line(std::istream& is) {
std::string line;
while (std::getline(is, line)) {
std::cout << line << std::endl;
}
}
[/code]
还是使用std::istream的成员函数getline()?
[code lang="cpp" title="代码2"]
void read_line_by_line(std::istream& is) {
char line[BUFFER_SIZE];
while (is.getline(line, BUFFER_SIZE)) {
std::cout << line << std::endl;
}
}
[/code]
有趣的是,使用成员函数的代码反倒是C风格,使用全局函数的代码却是C++风格。从性能上看,代码2比代码1要高效得多,因为代码1每读一行就会重新分配一次存储空间(有待验证),而代码2则不必如此。
代码2的缺点在于常量BUFFER_SIZE。如果BUFFER_SIZE小于某行有暗香盈袖长度,while循环便会在读取这一行时退出。性能的提升往往伴随着灵活性的下降。
当然,性能和灵活性并非总是反比的关系,灵活性不好时,性能也未必高。下面这个函数,取自我们实际的项目(便于阐述,略作改动),它在日期时间字符串之间做转换,并且输入输出格式恒定。
[code lang="cpp"]
// \param date "yyyymmdd", such as "20100114"
// \param time "hhmmss", such as "090251"
// \param dateTime "yyyy-MM-ddThh:mm:ss", such as 2010-01-14T09:02:51
const std::string& GmtFromDicomDataTime(const std::string& date,
const std::string& time,
std::string& dateTime)
{
dateTime = date.substr(0, 4) + "-" +
date.substr(4, 2) + "-" +
date.substr(6, 2) + "T" +
time.substr(0, 2) + ":" +
time.substr(2, 2) + ":" +
time.substr(4, 2);
return dateTime;
}
[/code]
这个函数共调用了6次substr(),和10次+操作符。让我们来算算共有多少个临时的字符串。6次substr()调用产生6个临时字符串,10次+操作符产生9个临时字符串,所以一共是15个。我想,没有比这个再多的实现了。
如果要优化的话,鉴于格式恒定,可以先分配好输出字符串的存储,然后将输入字符串的各部分对应到输出字符串中。
[code lang="cpp"]
const std::string& GmtFromDicomDataTime(const std::string& date,
const std::string& time,
std::string& dateTime)
{
dateTime.resize(20);
memcpy(&dateTime[0], &date[0], 4);
dateTime[4] = '-';
memcpy(&dateTime[5], &date[4], 2);
dateTime[7] = '-';
memcpy(&dateTime[8], &date[6], 2);
dateTime[10] = 'T';
memcpy(&dateTime[11], &time[0], 2);
dateTime[13] = ':';
memcpy(&dateTime[14], &time[2], 2);
dateTime[16] = ':';
memcpy(&dateTime[17], &time[4], 2);
return dateTime;
}
[/code]
如代码所示,灵活性没有任何改进,但是毋庸置疑的是,性能肯定有所提高,因为现在不会产生任何临时字符串了。
回到按行读取文本文件的例子,下面这个函数取自一个代码格式化工具,AStyle。我是无意中看到这段代码的,颇为震惊,因为它每次只读一个字符,并且每读一个字符后就调用一次std::string的append()。
[code lang="cpp" title="astyle_main.cpp"]
template<typename T>
string ASStreamIterator<T>::peekNextLine()
{
// ...
// read the next record
inStream->get(ch);
while (!inStream->eof() && ch != '\n' && ch != '\r')
{
nextLine_.append(1, ch);
inStream->get(ch);
}
// ...
}
[/code]
跟这段代码相比,使用std::getline()实在太高效了。有些开源软件的代码质量,实在不敢恭维。
