std :: unordered_map - 使用Lambda专门化KeyEqual

时间:2016-04-03 14:03:24

标签: c++ c++11 visual-c++ lambda

我想知道是否可以像这样创建td类型的变量:

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运算符的类型,但是visual studio不会让我编译它,说它缺少一个类型, lambda代表。

3 个答案:

答案 0 :(得分:2)

您必须在构造函数中定义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;
    }
);

答案 1 :(得分:2)

首先,正如Richard Hodges在答案中所说,你不能使用std::weak_ptr作为关键,因为它不稳定。忽略这一点,我们可以看一般问题:我们可以将lambdas用于模板参数。

通用解决方案是按照the following answer中的描述进行的。有两件事需要注意

  1. 必须单独定义lambda。
  2. lambda必须作为参数传入。
  3. 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};
    

    第一个参数是初始存储桶大小,并且是实现定义的。我假设在插入第一个项目时实现将找到优化值,我使用0。第二个是哈希函数,最后是lambda。

    我们可以将decltype(...)替换为function<bool(A,A)>来改善它。这允许我们在头中声明类型,因此将其传递给其他函数,而不需要共享实际的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;
    }
    

    这可以使用命令g++ map_lambda.cpp --std=c++11 -o map_lambda在Ubuntu 15.10上编译。

    我个人的意见是使用最后的解决方案,除非需要使用不同的功能进行密钥比较。

答案 2 :(得分:1)

这是答案,我担心你不会喜欢它。

如果没有逻辑错误,您无法做的事情。

(此时请阅读代码下方的文字。这很重要!)

如果可行,那就是这样......

#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)查找性能。