std::unordered_map-使用Lambda专门化KeyEqual
std::unordered_map - specializing KeyEqual with Lambda
我想知道是否可以创建一个std::unordered_map
类型的变量,如下所示:
std::unordered_map<std::weak_ptr<A>, B, std::hash<std::weak_ptr<A>,
[](const std::weak_ptr<A>& lhs, const std::weak_ptr<B>& rhs) -> bool { return lhs.lock() == rhs.lock(); }>
我希望它能工作,因为KeyEqual
模板只需要一个实现()
运算符的类型,但visualstudio不允许我编译它,说它缺少lambda所在的类型。
首先,正如Richard Hodges在他的回答中所说,不能使用std::weak_ptr
作为密钥,因为它不稳定。忽略这一点,我们可以看看一般的问题:我们可以使用lambdas作为模板参数吗。
通用解决方案是按照以下答案中的描述进行操作。需要注意两件事
- lambda必须单独定义
- lambda必须作为参数传入
2)的原因是从编译器为lambda创建的类型中删除了默认构造函数。
对于std::set
来说,这还不错,但考虑一下std::unordered_map
,它没有采用单个密钥比较函数的构造函数:
auto compare = [](const A & lhs, const A & rhs) { return lhs==rhs; };
std::unordered_map<
A,
B,
std::hash<A>,
decltype(compare)
> map1{0, std::hash<A>{}, compare};
第一个参数是初始bucket大小,并且是由实现定义的。我使用0,假设在插入第一个项时实现将找到一个优化的值。第二个是散列函数,最后是lambda。
我们可以通过用function<bool(A,A)>
替换decltype(...)
来稍微改进它。这允许我们在头中声明类型,从而将其传递给其他函数,而不需要共享实际的lambda。声明将变为:
typedef std::unordered_map<
A,
B,
std::hash<A>,
std::function<bool(A,A)>
> custom_unordered_map;
它可以如下使用:
custom_unordered_map map2{0, std::hash<A>{},
[](const A & lhs, const A & rhs) { return lhs==rhs; } };
此解决方案将允许直接使用自定义lambda,也可以使用免费函数。
此解决方案的主要好处是,它允许使用不同的比较函数,但使用起来非常冗长。
如果只需要一个比较函数,(对于该类型的用户)一个不太详细的解决方案是定义一个函数:
struct Compare {
bool operator () (const A & lhs, const A & rhs) {
return lhs==rhs;
}
};
然后可以以正常方式使用:
std::unordered_map<A, B, std::hash<A>, Compare> map4;
注意:使用此解决方案,可以使用默认构造函数。
以下是一个完整的示例:
#include <functional>
#include <memory>
#include <unordered_map>
using A = int;
using B = int;
struct Compare {
bool operator () (const A & lhs, const A & rhs) {
return lhs==rhs;
}
};
bool compare_func(const A & lhs, const A & rhs) {
return lhs==rhs;
}
int main() {
// Using lamda: default constructor is deleted, so the lambda
// must be passed as argument to the constructor.
auto compare = [](const A & lhs, const A & rhs) { return lhs==rhs; };
std::unordered_map<
A,
B,
std::hash<A>,
decltype(compare)
> map1{0, std::hash<A>{}, compare};
// Alternative: use std::function. More general, and allows any lambda to be used
typedef std::unordered_map<
A,
B,
std::hash<A>,
std::function<bool(A,A)>
> custom_unordered_map;
custom_unordered_map map2{0, std::hash<A>{},
[](const A & lhs, const A & rhs) { return lhs==rhs; } };
custom_unordered_map map3{0, std::hash<A>{}, compare_func};
// Use of function class
std::unordered_map<A, B, std::hash<A>, Compare> map4;
}
这可以在Ubuntu 15.10上使用命令g++ map_lambda.cpp --std=c++11 -o map_lambda
进行编译。
我个人的意见是使用最后一种解决方案,除非需要使用不同的函数进行密钥比较。
您必须在构造函数中定义lambda实现。这里有如何做到这一点的例子。
auto hash = std::unordered_map<std::string, int, std::hash<std::string>, std::function<bool(const std::string&, const std::string&) >>(0, std::hash<std::string>(),
[](const std::string& lhs, const std::string& rhs)
{
return lhs == rhs;
}
);
这是答案,恐怕你不会喜欢的。
如果没有逻辑错误,你想做的事情是不可能的。
(此时请阅读代码下方的文本。这很重要!)
如果它能起作用,它会是这样的。。。
#include <unordered_map>
#include <string>
#include <cstdint>
#include <utility>
#include <cassert>
struct A {};
struct B{};
int main()
{
auto owner_equal = [](const auto& l, const auto& r)
{
return not l.owner_before(r)
and not r.owner_before(l);
};
using equal_type = decltype(owner_equal);
std::unordered_map<
std::weak_ptr<A>,
B,
std::hash<std::weak_ptr<A>>,
equal_type> my_map;
auto pa = std::make_shared<A>;
my_map.emplace(pa, B{});
auto wa = std::weak_ptr<A>(pa);
pa.reset();
assert(my_map.find(wa) != my_map.end());
}
那么问题出在哪里呢?
你知道我是如何比较weak_ptr的相等性的吗?这是唯一安全的方法来知道他们是否指的是同一个对象。您必须比较控制块的地址。这是因为,一旦插入到地图中,最后一个shared_ptr可能会消失。
下次在映射的密钥中锁定weak_ptr以获取对象地址时,您将获得nullptr。因此密钥是不稳定的,这是对unordered_map的要求。
以及?
计算哈希也有同样的问题,但更糟。因为您无法访问控制块的地址,只能访问它相对于其他块的相对顺序。
因此,您无法可靠地计算weak_ptr的哈希,因为当您调用lock()时,后续两次调用的答案可能不同。
真的没有解决方案吗?
绝对不是。lock()
解决方案在测试中似乎可以工作,但当您的代码进入生产时,它会随机地莫名其妙地失败。
然后呢?
您必须使用std::map
并接受O(logN)查找性能。
- lambda参数转换为constexpr技巧,然后获取带链接的数组
- 可组合的lambda/std::函数与std::可选
- C++Boost Asio Pool线程,带有lambda函数和传递引用变量
- 如何建立使用模板函数的lambda函数的尾部返回类型
- 是否可以对零模板参数进行模板专门化
- 如何将lambda作为模板类的成员函数参数
- C++从其他 constexpr 创建 lambda 不能按顺序执行 Constexpr
- 在 lambda 捕获中声明的变量的类型推导
- 我可以将调用类的"this"传递给 lambda 函数吗?
- 为什么lambda在clang上崩溃而不是在gcc上崩溃
- 模板函数指针和lambda
- 两组使用lambda函数的大括号
- 使lambda不可复制/不可移动
- FLTK:按下哪个按钮 - 将数字传递给按钮的回调 (lambda)
- 尝试将lambda函数放在队列中时出现一般分配器错误(可能是与unique_ptr有关的错误)
- 将带有unique_ptr的可变 lambda 传递给 const&std::function
- AWS Lambda C++运行时权限被拒绝
- 捕获lambda中的std::数组
- std::unordered_map-使用Lambda专门化KeyEqual
- c++ 11 lambda和模板专门化