【问题标题】:Efficiently erase a unique_ptr from an unordered_set有效地从 unordered_set 中擦除 unique_ptr
【发布时间】:2020-05-29 21:25:30
【问题描述】:

我使用unique_ptrs 将一些对象的所有权存储在unordered_set 中。 但我不知道有什么好的方法可以在时机成熟时将其中一个从片场中删除。

代码如下所示:

typedef unique_ptr<MyType> MyPtr;

unordered_set<MyPtr> owner;

MyPtr p = make_unique<MyType>("foo")
MyType *pRaw = p.get();
owner.insert(std::move(p));

// Later ...

// I want to do something like this (cannot be written as-is, of course):
// owner.erase(pRaw);

有没有办法做到这一点? 当然,我可以使用 begin()end() 迭代整个集合,但将它们放入集合中的全部意义在于提高这些查找效率。

我已经想到了一些事情:

  • 使用shared_ptr。对于我的情况,这是错误的抽象。所有权是独一无二的。
  • 使用原始指针,忘记unique_ptr。这放弃了unique_ptr 提供的所有优势。
  • 使用unordered_set::begin(key) 查找存储桶。据我所知,我无法创建与我要删除的unique_ptr 匹配的密钥。但我很高兴被证明是错误的(:

(事实上,我使用eastl::unordered_set 解决了这个问题,它的find_as 函数用于自定义键)

【问题讨论】:

  • erase 有什么问题?
  • @Eljay unique_ptr == raw_ptr 格式不正确。
  • @Eljay:erase 采用迭代器。我如何获得迭代器? erase 的另一种形式需要 unique_ptr,但我无法创建其中一个,因为它们是......独一无二的(:
  • 使用这里提到的unordered_map&lt;rawptr, uniqptr&gt;stackoverflow.com/q/18939882 或等到 c++20 获得模板化的 find 方法 en.cppreference.com/w/cpp/container/unordered_set/find
  • 嗯,我在想,当您插入时存储返回的迭代器,而当您想擦除时使用该迭代器。喜欢auto pRaw_iter = owner.insert(std::move(p)).first; owner.erase(pRaw_iter) /* instead of owner.erase(pRaw) */;。如果您确定要删除哪些内容,则此方法有效,但如果这是“待确定”,那么这将非常痛苦...

标签: c++ c++14 unique-ptr unordered-set


【解决方案1】:

这是一个棘手的案例。 erase 有一个带有const key_type&amp; 参数的重载,因此我们可以尝试创建一个“陈旧的”unique_ptr 来获取要擦除的元素的哈希值:

template <typename T>
auto erase(std::unordered_set<std::unique_ptr<T>>& set, T* ptr)
{
    std::unique_ptr<T> stale_ptr{ptr};
    auto ret = set.erase(stale_ptr);
    stale_ptr.release();
    return ret;
}

(live demo)


然而,这个版本通常不是异常安全的,因为如果set.erase 抛出异常,release 将不会被调用。在这种情况下这不是问题,因为std::equal_to&lt;std::unique_ptr&lt;T&gt;&gt;::operator() 从不抛出异常。在一般情况下,我们可以滥用unique_ptr (!) 通过确保无论函数是正常退出还是异常退出都调用release 来强制执行异常安全:

template <typename T>
auto erase(std::unordered_set<std::unique_ptr<T>>& set, T* ptr)
{
    std::unique_ptr<T> stale_ptr{ptr};

    auto release = [](std::unique_ptr<T>* p) { p->release(); };
    std::unique_ptr<std::unique_ptr<T>, decltype(release)> release_helper{&stale_ptr, release};

    return set.erase(stale_ptr);
}

(live demo)

【讨论】:

  • 太糟糕了!谢谢(:
  • 我们可以给stale_ptr 一个自定义删除器吗?即auto release = [](T* p){};std::unique_ptr&lt;T,decltype(release)&gt; stale_ptr{ptr, release};return set.erase(stale_ptr); 这将是异常安全的,因为release 将在unique_ptr 的析构函数中被调用。
  • @Bernard 直接向stale_ptr 提供删除器会很好,但不幸的是,erase 重载只接受const key_type&amp;,所以set.erase(stale_ptr) 会吐出错误:(
【解决方案2】:

在 C++20 中,std::unordered_set::find 可以将 equivalent 键与 transparent 散列和 KeyEqual 一起使用,那么您可能会执行类似的操作:

struct MyHash
{
    using is_transparent = void;

    auto operator()(MyType* p) const { return std::hash<MyType*>{}(p); }
    auto operator()(const MyPtr& p) const { return std::hash<MyType*>{}(p.get()); }
};

struct MyEqual
{
    using is_transparent = void;

    template <typename LHS, typename RHS>
    auto operator()(const LHS& lhs, const RHS& rhs) const
    {
        return AsPtr(lhs) == AsPtr(rhs);
    }
private:
    static const MyType* AsPtr(const MyType* p) { return p; }
    static const MyType* AsPtr(const MyPtr& p) { return p.get(); }

};

int main()
{
    std::unordered_set<MyPtr, MyHash, MyEqual> owner;

    MyPtr p = std::make_unique<MyType>();
    MyType *pRaw = p.get();
    owner.insert(std::move(p));

    auto it = owner.find(pRaw);
    if (it != owner.end()) {
        owner.erase(it);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多