C++函数字符串参数的优化
[code lang="cpp"]
void findByName(const std:string& name) {
// ...
}
findByName("adam"); // 这里会产生一个临时std::string对象。
[/code]
有时候,这种临时对象是不可避免的,假如findByName在内部只接受和处理std::string,这时,即使参数上是const char*,到了里面还是要生成std::string对象。但是很多时候,这种临时对象都是可以避免的,std::string做得就很好,比如成员函数compare,它既接受const std::string&也接受const char*,find等成员函数也是如此。
假如你想写一个函数,把字符串转换成bool:
[code lang="cpp"]
// "true"/"false" (case ignored) -> true/false
// "1"/"0" -> true/false
... BoolFromString(...)
[/code]
首先提供参数为const char*的实现:
[code lang="cpp"]
bool BoolFromString(const char* input, bool* output) {
if (_stricmp(input, "true") == 0 || strcmp(input, "1") == 0) {
*output = true;
return true;
} else if (_stricmp(input, "false") == 0 || strcmp(input, "0") == 0) {
*output = false;
return true;
}
return false;
}
[/code]
然后,参数为const std::string&的实现只是简单的转调:
[code lang="cpp"]
bool BoolFromString(const std::string& input, bool* output) {
return BoolFromString(input.c_str(), output);
}
[/code]
这里很容易犯的错误是把转调的关系搞反:
[code lang="cpp"]
bool BoolFromString(const char* input, bool* output) {
return BoolFromString(std::string(input), output);
}
[/code]
这样的实现没有任何意义,干脆不要提供const char*的实现。
这种字符串参数的优化看似微不足道,但不可小觑,对于频繁处理字符串的程序(比如编译器),必然是可观的性能改进。
这种改进实施起来对程序员的素质要求比较高,于是就有了辅助的手段:Goolge Chromium提供了StringPiece类,而LLVM则提供了StringRef类。
StringPiece是一个指向一片固定大小内存的类字符串对象(注释原文:A string-like object that points to a sized piece of memory),类的定义大致如下:
[code lang="cpp"]
class StringPiece {
public:
typedef size_t size_type;
private:
const char* ptr_;
size_type length_;
public:
StringPiece() : ptr_(NULL), length_(0) { }
StringPiece(const char* str)
: ptr_(str), length_((str == NULL) ? 0 : strlen(str)) { }
StringPiece(const std::string& str)
: ptr_(str.data()), length_(str.size()) { }
StringPiece(const char* offset, size_type len)
: ptr_(offset), length_(len) { }
const char* data() const { return ptr_; }
size_type size() const { return length_; }
size_type length() const { return length_; }
bool empty() const { return length_ == 0; }
[/code]
StringPiece可由const char*,std::string或者一块非'\0'结尾的字符数组构造,但它不会拷贝内存。这就是它的特点。它所指向的内存必须要有较其自身更长的生命期,这便决定了它比较适合作为函数参数,而不是作为成员变量或者放在容器中。
使用StringPiece来实现BoolFromString,不必有两个函数,一个就够了:
[code lang="cpp"]
bool BoolFromString(const StringPiece& input, bool* output) {
if (_stricmp(input.data(), "true") == 0 || strcmp(input.data(), "1") == 0) {
*output = true;
return true;
} else if (_stricmp(input.data(), "false") == 0 || strcmp(input.data(), "0") == 0) {
*output = false;
return true;
}
return false;
}
[/code]
这个BoolFromString可以接受const char*,std::string,甚至StringPiece,不管哪一种,都没有关于字符串的多余的内存分配和释放。
LLVM的StringRef,实现上跟Google的StringPiece一样,接口方面略有差别。作为参数时,Google建议StringPiece用const&,而LLVM则直接传值。StringRef也可以传const&,但是它的名字起得不好,已经带有引用的含义了(Ref),效率上当然传const&更高。所以我更喜欢Google的StringPiece。