Эффективно стереть unique_ptr из unordered_set - PullRequest
3 голосов
/ 14 февраля 2020

Я храню права собственности на некоторые объекты внутри unordered_set, используя unique_ptr s. Но я не знаю хорошего способа вычеркнуть одного из них из набора, когда придет время.

Код выглядит примерно так:

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 для пользовательских клавиш)

Ответы [ 2 ]

2 голосов
/ 14 февраля 2020

В C ++ 20 std :: unordered_set :: find может использовать эквивалент ключ с прозрачным га sh и 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);
    }
}
2 голосов
/ 14 февраля 2020

Это сложный случай. erase имеет перегрузку, которая принимает параметр const key_type&, поэтому мы можем попытаться создать "устаревший" unique_ptr, чтобы получить значение ha sh удаляемого элемента:

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 )


Эта версия, однако, в целом не является безопасной для исключения, поскольку release не будет вызываться, если set.erase выдает исключение. В этом случае это не проблема, поскольку std::equal_to<std::unique_ptr<T>>::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);
}

( живое демо )

...