考虑以下代码:
bool fun(const string& code)
{
assert(code.length() >= 2);
if (code.substr(0, 2) == string("XX"))
{
// ...
}
// ...
}
有没有发现什么问题? 不要纠结于assert(), 它只是为了保证 string “code” 长度大于2而已.
很显然, 这段代码用来检查string是否以"XX"开头. 基于它长度大于2 的前提, 这段代码能正常运行. 我们的关心的问题是, 表达式能否达到正确的结果.
绝大多数情况下, 我们之所以使用C++, 是希望能使我们的程序达到最优的性能. 基于这个目标, 上面的代码看起来就不是很正确了. 为了检查"code"是否以"XX"开头, 我们生成了两个临时的string, 每个string都可能潜在地申请堆上的内存. 有人可能会为此辩解: std::string应该能为一个 2字母的序列实现 短字符串最优化(SSO). 就算这个辩解是正确的, 这段代码也已经 耗费了 一些不能被优化掉的开销, 更何况, 并不是所有的都会实现SSO. 例如, 我使用的GCC 4.4.7 就不会为string实现SSO.
类模板std::basic_string
的接口很复杂. 它提供了大量的成员函数, 似乎不用它们显得不领情, 同时开发者也不会有自己一遍遍重新解析的冲动.
因为开发者模糊地记得应用于NTBS(null-terminated byte strings)(可以被隐式地转为const char*
)的 操作符 ==
会使结果出错, 所以他通过 确保参与比较的两个值都是std::string
类型来避开这个错误. 他可能在想, 在运行操作符==
前文本"XX" 已经被显式地转成了std::string
, 那么这么做也没有坏处. 但是, 这是错误的, 因为对于操作符==
, 标准提供了两种版本:
bool operator==(const std::string& lhs, const char* rhs);
bool operator==(const char* lhs, const std::string& rhs);
当然实际上他们是带有多个参数的函数模板, 远比这个复杂. std::string
可以直接跟NTBS比较, 没有必要生成临时的std::string
. 我们开头的例子, 可以通过去除显式生成的临时副本 进行优化: if (code.substr(0, 2) == "XX")
更进一步, 不可否认, 在有些地方使用操作符==
看起来很高雅, 但是仅仅为了检查一个string
本身的一部分而去新申请一部分资源(生成一个新的string
) 这种做法是错误的. 开发者的初衷, 并不是要是程序看起来高雅. 实际上, 如果我们深入研究std::basic_string
的官方文档, 就会发现, std::basic_string
提供了一种比较它的子字符串和NTBS的方法: if(code.compare(0, 2, "XX") == 0)
这个比较是三方比较, 结果等于0表示相等. 它可以达到目的, 并且不需要生成任何临时的string
.
尽管这个compare()
使性能达到了很大的优化, 但我并不满足于此. 虽然它做了正确的事情, 但如果我们是第一次遇到他, 很难抓住他的精髓. 如果你可以使用boost库, 我的建议性的解决方案是使用Boost String Algorithms Library
中的算法:
#include <boost algorithm="" string="" predicate.hpp=""></boost>
bool func(const string& code)
{
if (boost::algorithm::starts_with(code, "XX"))
}
这段代码很好地体现了我想说的意思, 没有任何多余的开销.
原文地址: https://akrzemi1.wordpress.com/2015/04/15/strings-interface/