Использование std:: unordered_set из std:: unique_ptr
Предположим, что у меня есть набор unique_ptr:
std::unordered_set <std::unique_ptr <MyClass>> my_set;
Я не уверен, какой безопасный способ проверить, существует ли данный указатель в наборе. Обычный способ сделать это может состоять в вызове my_set.find ()
, но что я передаю в качестве параметра?
Все, что у меня снаружи, является необработанным указателем. Поэтому я должен создать еще один уникальный_ptr из указателя, передать его в find()
, а затем release()
этот указатель, иначе объект будет разрушен (дважды). Конечно, этот процесс может быть выполнен в функции, поэтому вызывающий может передать необработанный указатель, и я делаю преобразования.
Безопасен ли этот метод? Есть ли лучший способ работать с набором unique_ptr?
Ответы
Ответ 1
Вы также можете использовать удаляющий файл, который необязательно ничего не делает.
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));
Пример в реальном времени.
Ответ 2
Обратите внимание, что возможность выполнения гетерогенного поиска на стандартных контейнерах является предметом некоторых предложений.
http://cplusplus.github.io/LWG/lwg-proposal-status.html списки
- 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;
}
Предупреждение Это также очень неэффективно, конечно.
Ответ 3
Вместо набора можно использовать std::map<MyClass*, std::unique_ptr<MyClass>>
. Затем вы можете добавить такие элементы:
std::unique_ptr<MyClass> instance(new MyClass);
map.emplace(instance.get(), std::move(instance));
Ответ 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
не требует, чтобы целевое значение имело тот же тип, что и
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 );
}
};
Вставка может включать в себя несколько ходов, но перемещение
a std::unique_ptr
должен быть довольно дешевым, а улучшенный
местность этого решения может компенсировать дополнительное время выполнения
которые он в противном случае налагает.