【问题标题】:Copy-assigning an unordered map with incompatible allocators使用不兼容的分配器复制分配无序映射
【发布时间】:2018-10-09 09:05:51
【问题描述】:

考虑以下虚拟分配器(为示例而创建):

template<typename T> class C
{
public:
    typedef T value_type;

    C() = default;

    template<typename U>
    C(C<U> const &a)
    {}


    T* allocate(std::size_t n, T const* = nullptr)
    {
        return new T[n];
    }

    void deallocate(T* p, std::size_t n)
    {
        return;
    }

    typedef value_type       *pointer;
    typedef const value_type *const_pointer;
    typedef value_type       &      reference;
    typedef value_type const &const_reference;
    typedef std::size_t       size_type;
    typedef std::ptrdiff_t    difference_type;
    static       pointer address(reference x) { return &x; }
    static const_pointer address(const_reference x) { return &x; }
    static size_type max_size() { return std::numeric_limits<size_type>::max(); }
    template <typename U> static void destroy(U* ptr) { ptr->~U(); }
    template <typename U> struct rebind { using other = C<U>; };

    template<typename U, typename... Args>
    static void construct(U* ptr, Args&&... args) {
        new (ptr) U(std::forward<Args>(args)...);
    }
};

template<class T1, class T2>
bool operator==(C<T1> const& lhs, C<T2> const& rhs)
{
    return std::addressof(lhs) == std::addressof(rhs);
}

template<class T1, class T2>
bool operator!=(C<T1> const& lhs, C<T2> const& rhs)
{
    return !(lhs == rhs);
}

大部分代码都是样板代码。关键的细节是分配器的任何两个实例都将被认为是不兼容的——bool operator== 总是返回false。当我尝试将此分配器与大多数 STL 容器(例如 std::vector)一起使用来复制分配非常简单的元素时,例如:

std::vector<int, C<int>> a;
a = std::vector<int, C<int>>();

一切正常,我得到了预期的行为。但是,当我做同样的事情,但使用std::unordered_map 时,我在需要支持的两个平台上得到不同的行为。在带有 GCC 7.1 的 Linux 上,我继续得到预期的行为。然而,在带有 VS 2015 的 Windows 上,我在标题为 xmemory0 的 VS 标头中收到声明失败的声明 containers incompatible for swap。请注意,用于std::unordered_map 的代码与上面用于std::vector 的代码几乎相同:

using B = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, C<std::pair<int const, int>>>;
B b;
b = B();

我的分配器是否存在固有问题,而 GCC 7.1 给了我未定义的行为?如果不是,这是 VS 2015 运行时库的故障吗?如果是这样,为什么这个故障只出现在unordered_map

【问题讨论】:

    标签: c++ unordered-map allocator


    【解决方案1】:

    你不能拥有唯一拥有状态的分配器,它们是must beCopyConstructible。例如。你应该从std::unique_ptrs 切换到std::shared_ptrs。

    你应该放松你的比较

    template<class T1, class T2>
    bool operator==(C<T1> const& lhs, C<T2> const& rhs)
    {
        return /* check equality of some member of C */;
    }
    
    template<class T1, class T2>
    bool operator!=(C<T1> const& lhs, C<T2> const& rhs)
    {
        return !(lhs == rhs);
    }
    

    您也可能受益于遵守Rule of zero/five,并将propogate_on_container_copy_assignmentpropogate_on_container_move_assignmentpropogate_on_container_swap 定义为std::true_type

    关于 MSVC 在哪里出错的提示

    注意:如果propagate_on_container_swapfalse,则使用不相等的分配器交换两个容器是未定义的行为。

    【讨论】:

    • 这行得通,谢谢。你知道为什么 MSVC 只在unordered_maps 上跳闸吗?这些有什么特别之处? (注意:这纯粹是出于我自己的好奇心,您已经回答了主要问题 - 再次感谢。)
    • 我只能猜测。这可能是因为将分配器重新绑定到节点类型
    • 谢谢。关于编辑 - 我的代码库中实际拥有的初始分配器有一个更复杂的operator==,但我注意到问题仅在它返回false 时发生(因此显式设置为false 以强制问题)。当我用你的建议替换我的operator==(和operator!=)时,不幸的是我仍然得到相同的行为。
    • 这个答案是错误的。 Copy-ctor 或一致性分配器的分配必须产生一个相等的分配器。请参阅 T.C. 的回答。
    • @L.F.是的,终于到了。我一直怀疑==
    【解决方案2】:

    这不是一个符合要求的分配器。分配器的所有副本,包括反弹的副本,必须相互比较。


    另外,unordered_mapvalue_typepair&lt;const Key, Value&gt;,所以您的示例应该使用C&lt;pair&lt;const int, int&gt;&gt;

    【讨论】:

    • 会使用例如return std::addressof(lhs) == std::addressof(rhs),正如用户 Caleth 在他的上述答案中所建议的那样来解决这个问题吗?如果是这样,我已经这样做了,我仍然得到相同的行为 - 我可以将其编辑到问题中以使其更好。关于intconst int - 我也进行了更改(再次修改问题),但结果没有区别。
    • 没有。只要副本不相等,就会出现未定义的行为。
    • @L.F.:很公平——你能指点我引用的标准吗?我对分配器的了解非常有限(因为我通常只使用std::allocator),而且我总是很乐意了解更多信息。
    • @user74427 eel.is/c++draft/…
    • 注意这一点:X u(a); X u = a; 不能通过异常退出。确保:u == a
    猜你喜欢
    • 2023-03-20
    • 1970-01-01
    • 1970-01-01
    • 2012-06-26
    • 1970-01-01
    • 1970-01-01
    • 2010-11-20
    • 2013-11-19
    • 2017-12-04
    相关资源
    最近更新 更多