身為一個 C++ 程序員,我都會盡我所能避免不必要的計算。所以當我聽到 C++ 17 新增了 std::string_view
類別,我就迫不及待的想要使用它。std::string_view
的概念是:你可以指稱另一個 std::string
的一個子字串,但是不複製這段子字串。例如:
std::string s("xxx yyy zz");
std::string_view sv(s);
std::string_view x(sv.substr(0, 3)); // x == "xxx"
std::string_view y(sv.substr(4, 3)); // y == "yyy"
std::string_view z(sv.substr(8, 2)); // z == "zz"
一個典型的使用方法是用來實作字串分割的函式:
std::vector<std::string_view> tokenize(std::string_view sv) {
static const char delims[] = " \t";
std::vector<std::string_view> res;
std::string::size_type pos = 0;
while (true) {
pos = sv.find_first_not_of(delims, pos);
if (pos == std::string::npos) {
break;
}
std::string::size_type end = sv.find_first_of(delims, pos + 1);
if (end == std::string::npos) {
res.push_back(sv.substr(pos));
break;
}
res.push_back(sv.substr(pos, end - pos));
pos = end + 1;
}
return res;
}
不過值得一提的是:使用 std::string_view
的時候必需注意他的指稱的 std::string
的生命週期。如果被 std::string_view
指稱的 std::string
物件先被解構了,繼續使用 std::string_view
就可能會有未定義結果。
接下來稍微轉換一下話題。因為 std::string_view
對指稱的 std::string
物件的生命週期有額外的要求,所以在一般情境下,使用 std::string
當 std::map
的索引型別 (Key Type) 會是比較好的選擇:
std::map<std::string, int> m{{"x", 1}, {"y", 2}, {"z", 3}};
但這會衍生出一個問題:我們在呼叫 std::map::find()
的時候,只能傳 std::string
。如果我們直接輸入以下程式,我們會得到編譯錯誤:
void test(std::string_view k) {
auto &&it = m.find(k); // XXX: compile error
// ...
}
這個錯誤的成因是容器的索引型別和 find()
的參數型別不一致。一個簡單但不好的解決方式是把 std::string_view
明確地轉換成 std::string
:
void test(std::string_view k) {
auto &&it = m.find(std::string(k)); // explicit cast
// ...
}
這個方法會引入非必要的浪費。上面這段程式碼會建構一個暫時的 std::string
物件,用這個 std::string
物件當索引,再解構這個暫時物件。而且這個暫時物件會複製 std::string_view
的「內容」,這正是我們想要避免的。
更好的解法是使用 C++ 14 新增的「異質比較查詢 (Heterogeneous Comparison Lookup)」。要使用異質比較查詢,容器的比較函式物件 (Comparator) 必須定義 is_transparent
成員型別。在 functional 標頭檔裡面的 std::less<>
(不帶參數)就剛好符合這個要求:
#include <functional>
std::map<std::string, int, std::less<>> m2{
{"x", 1}, {"y", 2}, {"z", 3}};
void test(std::string_view k) {
auto &&it = m2.find(k); // OK
// ...
}
std::less<>
雖然很好用,但是他非常的自由。以上面的例子來說,m2.find()
可以接收任何可以和 std::string
比較的物件。如果我們不想要有這樣的行為,我們也可以自己寫一個比較函式物件:
struct string_less {
using is_transparent = void;
bool operator()(const std::string &lhs,
const std::string &rhs) const {
return lhs < rhs;
}
bool operator()(const std::string &lhs,
std::string_view rhs) const {
return lhs < rhs;
}
bool operator()(std::string_view lhs,
const std::string &rhs) const {
return lhs < rhs;
}
};
希望這篇文章有清楚地介紹 std::string_view
和異質比較查詢。我們下次見囉。