C++ unordered_map<字符串,...> 查找而不构造字符串

C++ unordered_map<string, ...> lookup without constructing string

本文关键字:字符串 查找 gt lt unordered C++ map      更新时间:2023-10-16

我有C++代码来调查一个大字符串并匹配许多子字符串。 我尽可能避免构造 std::strings,通过像这样编码子字符串:

char* buffer, size_t bufferSize

但是,在某些时候,我想在其中一个中查找一个子字符串:

std::unordered_map<std::string, Info> stringToInfo = {...

所以,要做到这一点,我去:

stringToInfo.find(std::string(buffer, bufferSize))

这将构造一个 std::string 的唯一目的是查找。

我觉得我可以在这里做一个优化,通过...将unordered_map的键类型更改为某种临时字符串冒名顶替者,像这样的类......

class SubString
{
char* buffer;
size_t bufferSize;
// ...
};

。这与 std::string 执行相同的逻辑来哈希和比较,但在它被销毁时不会释放其缓冲区。

所以,我的问题是:有没有办法让标准类来做到这一点,还是我自己编写这个类?

你想要做的称为异构查找。 自 C++14 以来,std::map::findstd::set::find都支持它(请注意函数的版本 (3) 和 (4),它们在查找值类型上模板化)。 对于无序容器来说,这更复杂,因为它们需要被告知或找到所有键类型的哈希函数,这些函数将为相同的文本生成相同的哈希值。 正在考虑一项关于未来标准的提案:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0919r0.html

同时,您可以使用另一个已经支持异构查找的库,例如boost::unordered_map::find.

如果你想坚持std::unordered_map,你可以通过在unordered_map旁边存储一个可以重新分配值的std::string成员来避免创建太多的字符串临时,然后将该string传递给find。 可以将其封装在自定义容器类中。

另一种方法是编写一个自定义类以用作无序容器键:

struct CharPtrOrString
{
const char* p_;
std::string s_;
explicit CharPtrOrString(const char* p) : p_{p} { }
CharPtrOrString(std::string s) : p_{nullptr}, s_{std::move(s)} { }
bool operator==(const CharPtrOrString& x) const
{
return p_ ? x.p_ ? std::strcmp(p_, x.p_) == 0
: p_ == x.s_
: x.p_ ? s_ == x.p_
: s_ == x.s_;
}
struct Hash
{
size_t operator()(const CharPtrOrString& x) const
{
std::string_view sv{x.p_ ? x.p_ : x.s_.c_str()};
return std::hash<std::string_view>()(sv);
} 
};
};

然后,您可以从std::string构造CharPtrOrString以用于无序容器密钥,但每次调用find时,都可以从您的const char*廉价地构建一个。 请注意,上面的operator==必须弄清楚你做了什么(使用的约定是,如果指针nullptr,那么std::string成员正在使用中),以便比较正在使用的成员。 哈希函数必须确保具有特定文本值的std::string将产生与const char*相同的哈希(默认情况下,GCC 7.3 和/或 Clang 6不会 - 我同时使用两者并记住一个有问题,但不是哪个)。

在 C++20 中,您现在可以执行以下操作:

// struct is from "https://www.cppstories.com/2021/heterogeneous-access-cpp20/"
struct string_hash {
using is_transparent = void;
[[nodiscard]] size_t operator()(const char *txt) const {
return std::hash<std::string_view>{}(txt);
}
[[nodiscard]] size_t operator()(std::string_view txt) const {
return std::hash<std::string_view>{}(txt);
}
[[nodiscard]] size_t operator()(const std::string &txt) const {
return std::hash<std::string>{}(txt);
}
};

// Declaration of map
std::unordered_map<std::string, Info, string_hash, std::equal_to<>> map;
std::string_view key = "foo";
if (map.find(key))
{
// do something here
}

请注意,使用[]时您仍然需要std::string。可能有办法解决这个问题,但我不太确定