我对内存分配和复制非常敏感。因此,如果函数需要
std::string
对象作为输入,我总是使用 const std::string &
。
最近,我发现如果我传入一个
const std::string &
,std::string
将会构造一个char[]
对象:
#include <iostream>
#include <string>
using namespace std;
void test_string(const std::string & s) { // use std::string_view here is better
// it can avoid local variable allocation
printf("%p\n", s.data());
}
int main() {
char aa[] = "asd";
test_string(aa);
printf("%p\n", aa);
}
我将
const std::string &
改为std::string_view
,解决了不必要的构建或复制。所以,我认为std::string_view
值得在任何地方使用。
我把所有
const std::string &
都换成了std::string_view
,如果有地方需要用std::string
,我就用.data()
来获取字符串。
以下情况会出现问题:
inline std::vector<std::string_view> Split(std::string_view str, std::string_view delim, const bool trim_empty = false) {
if (str.empty()) return {};
size_t pos, last_pos = 0, len;
std::vector<std::string_view> tokens;
while (true) {
pos = str.find(delim, last_pos);
if (pos == std::string::npos) pos = str.size();
len = pos - last_pos;
if (!trim_empty || len != 0) tokens.push_back(str.substr(last_pos, len));
if (pos == str.size()) break;
else last_pos = pos + delim.size();
}
return tokens;
}
我用它来将
string
拆分为 vector
,如您所见,std::string_view
避免了复制和大量内存分配。
但是问题发生在使用
std::string_view
:
std::string str = "asd\nbn\ncd\n\n";
std::unordered_map<std::string, int> m;
m["asd"] = 2;
const auto & v = Split(str, "\n");
const auto & it = m.find(v.front().data());
此操作失败,因为
v.front().data()
返回整个字符串,而不是第一部分。
我知道这是由于字符串流中没有
"\0"
造成的。
似乎没有什么好方法可以找到正确的
std::string_view
,除了使用std::string
构造一个std::string(v.front().data())
。
那么,有什么办法可以让
.data()
如期结束呢?或者用 const std::string &
替换 std::string_view
并不总是一个好的选择?
std::string_view::data()
返回一个不一定以 null 终止的指针。std::string::data()
返回一个 always 以 null 终止的指针 (C++11 起)。std::string
的char*
构造函数 9要求它指向以 null 结尾的字符串。
既然你无论如何都要构造临时
std::string
,请正确执行:std::string{v.front()}
将从 std::string_view
构造正确长度的字符串。
或者,如果这是
Split
的唯一用途,则根本不要使用 std::string_view
,如果仅使用 string_view
来构造 string
似乎不会有太多收获.
此外,在问题之外,但正如 Botje 在 comment 中注意到的那样,您对引用的使用是错误的。它们引用的对象都在相应行的
;
处消失,您只剩下悬空引用。
这里的主要问题是,您构建
std::string
的唯一目的是与 map.find
一起使用,在这种情况下,您可能想要调用接受 std::string
而不是传递 string_view
的构造函数char*
所以不需要调用strlen
。 (明确地做std::string{string_view}
)但这不是问题的真正解决方案。
通常
std::unordered_map
具有 find
并且比较器和哈希器不允许其他类型,因此如果 find
是 string_view
,则 Key
对于 std::string
会失败,您将必须定义自己的哈希器和比较器,并且您将需要 C++20 来进行重载,以接受与 find 的键相当的任何类型
#include <unordered_map>
#include <string>
#include <string_view>
#include <iostream>
struct Comparator
{
using is_transparent = std::true_type;
bool operator()(const auto& lhs, const auto& rhs) const {
auto result = lhs == rhs;
return result;
}
};
struct Hash
{
using is_transparent = std::true_type;
template <typename T>
size_t operator()(const T& obj) const {
auto result = std::hash<std::decay_t<T>>{}(obj);
return result;
}
};
int main()
{
std::unordered_map<std::string, int, Hash, Comparator> m{ {"hel",1} };
std::string s = "hello";
std::string_view sv{ s };
sv = sv.substr(0, 3);
auto it = m.find(sv);
if (it != m.end())
{
std::cout << "found!";
}
}
请注意,您需要 C++20 编译器来运行此代码,否则您将必须使用 Boost 的容器版本,该容器已经具有针对较低 C++ 版本的此重载,或具有此重载的任何其他实现。