Использование std :: unordered_set из std :: unique_ptr [duplicate]

17

Предположим, что у меня есть набор unique_ptr:

std::unordered_set <std::unique_ptr <MyClass>> my_set;

Я не уверен, что это безопасный способ проверить, существует ли данный указатель в наборе. Обычный способ сделать это может состоять в вызове my_set.find () , но что я передаю в качестве параметра?

Все, что у меня снаружи, является необработанным указателем. Поэтому мне нужно создать еще один уникальный_ptr из указателя, передать его find() , а затем release() этого указателя, иначе объект будет разрушен (дважды). Конечно, этот процесс может быть выполнен в функции, поэтому вызывающий может передать необработанный указатель, и я делаю преобразования.

Безопасен ли этот метод? Есть ли лучший способ работать с набором unique_ptr?

    
задан cfa45ca55111016ee9269f0a52e771 25.07.2013 в 08:56
источник

4 ответа

17

Вы также можете использовать удаляющий файл, который необязательно ничего не делает.

template<class T>
struct maybe_deleter{
  bool _delete;
  explicit maybe_deleter(bool doit = true) : _delete(doit){}

  void operator()(T* p) const{
    if(_delete) delete p;
  }
};

template<class T>
using set_unique_ptr = std::unique_ptr<T, maybe_deleter<T>>;

template<class T>
set_unique_ptr<T> make_find_ptr(T* raw){
    return set_unique_ptr<T>(raw, maybe_deleter<T>(false));
}

// ...

int* raw = new int(42);
std::unordered_set<set_unique_ptr<int>> myset;
myset.insert(set_unique_ptr<int>(raw));

auto it = myset.find(make_find_ptr(raw));

Пример в реальном времени.

    
ответ дан Xeo 25.07.2013 в 11:14
источник
10

Обратите внимание, что возможность выполнять гетерогенный поиск на стандартных контейнерах является предметом некоторых предложений.

Ссылка :

  • N3465 Добавление поиска гетерогенных сравнений в ассоциативные контейнеры для TR2 (Rev 2) [Обращаться с N3573]
  • N2882 id.
  • N3573 Гетерогенные расширения для неупорядоченных контейнеров [Handle with N3465]

Особенно последнее похоже, что оно будет охватывать ваш прецедент.

В настоящее время IMO не очень симпатичный, но работающий альтернативный метод обхода (O (n)):

#include <iterator>
#include <iostream>
#include <algorithm>

#include <unordered_set>
#include <memory>

#include <cassert>

struct MyClass {};

template <typename T>
struct RawEqualTo
{
    RawEqualTo(T const* raw) : raw(raw) {}

    bool operator()(T const* p) const  
        { return raw == p; }
    bool operator()(std::unique_ptr<T> const& up) const  
        { return raw == up.get(); }

  private:
    T const* raw;
};


using namespace std;
int main()
{
    std::unordered_set <std::unique_ptr <MyClass>> my_set;

    my_set.insert(std::unique_ptr<MyClass>(new MyClass));
    my_set.insert(std::unique_ptr<MyClass>(new MyClass));

    auto raw = my_set.begin()->get();

    bool found = end(my_set) != std::find_if(begin(my_set), end(my_set), RawEqualTo<MyClass>(raw));
    assert(found);

    raw = new MyClass;

    found = end(my_set) != std::find_if(begin(my_set), end(my_set), RawEqualTo<MyClass>(raw));
    assert(!found);

    delete raw;
}

Предупреждение Конечно, это тоже очень неэффективно.

    
ответ дан sehe 25.07.2013 в 10:01
источник
8

Вы можете использовать std::map<MyClass*, std::unique_ptr<MyClass>> вместо набора. Затем вы можете добавить такие элементы:

 std::unique_ptr<MyClass> instance(new MyClass);
 map.emplace(instance.get(), std::move(instance));
    
ответ дан petersohn 25.07.2013 в 09:04
источник
4

Если целью является постоянное время поиска, я не думаю, что есть решение. std::unordered_set<std::unique_ptr<MyClass>>::find требует std::unique_ptr<MyClass> в качестве аргумента. Вам придется либо изменить контейнер или изменить содержащийся тип.

Одна из возможных может заключаться в замене std::unique_ptr на std::shared_ptr , и измените остальную часть кода так, чтобы все MyClass помещаются в shared_ptr, как только они создаются, и управляются только с помощью общих указателей. Логически, это, вероятно, более согласовано: unique_ptr в значительной степени подразумевает (по его названию, а также его семантику), что там не являются другими указателями на объект. С другой стороны, вы можете не сможет использовать shared_ptr, если, например, MyClass имеет указатели на другие MyClass , которые могут построить цикл.

В противном случае, если вы можете принять доступ O (lg n), а не постоянный доступ (разница вообще не становится заметны до тех пор, пока таблицы не будут достаточно большими), вы можете использовать std::vector<MyClass> , используя std::lower_bound , чтобы сохранить его отсортирован. В отличие от std::unordered_set<>::find , std::lower_bound not требует, чтобы целевое значение имело тот же тип, что и value_type последовательности; все, что вам нужно сделать, это обеспечить что они сопоставимы, например, предоставляя объект Compare вдоль линий:

class MyClassPtrCompare
{
    std::less<MyClass const*> cmp;
public:
    bool operator()( std::unique_ptr<MyClass> const& lhs,
                     std::unique_ptr<MyClass> const& rhs ) const
    {
        return cmp( lhs.get(), rhs.get() );
    }
    bool operator()( MyClass const* lhs,
                     std::unique_ptr<MyClass> const& rhs ) const
    {
        return cmp( lhs, rhs.get() );
    }
    bool operator()( std::unique_ptr<MyClass> const& lhs,
                     MyClass const* rhs ) const
    {
        return cmp( lhs.get(), rhs );
    }
    bool operator()( MyClass const* lhs,
                     MyClass const* rhs ) const
    {
        return cmp( lhs, rhs );
    }
};

Вставка может включать в себя несколько ходов, но перемещение std::unique_ptr должно быть довольно дешевым, а улучшенный местность этого решения может компенсировать дополнительное время выполнения которые он в противном случае налагает.

    
ответ дан James Kanze 25.07.2013 в 10:42
источник