c++ - Usando un std:: unordered_set de std:: unique_ptr
c++11 stl (4)
Supongamos que tengo un conjunto de unique_ptr:
std::unordered_set <std::unique_ptr <MyClass>> my_set;
No estoy seguro de cuál es la forma segura de comprobar si existe un puntero dado en el conjunto. La forma normal de hacerlo es llamar a my_set.find ()
, pero ¿qué paso como parámetro?
Todo lo que tengo del exterior es un puntero crudo. Así que tengo que crear otro unique_ptr desde el puntero, pasarlo a find()
y luego release()
ese puntero, de lo contrario, el objeto se destruiría (dos veces). Por supuesto, este proceso se puede hacer en una función, por lo que la persona que llama puede pasar el puntero en bruto y yo hago las conversiones.
¿Es este método seguro? ¿Hay una mejor manera de trabajar con un conjunto de unique_ptr?
Puede usar un std::map<MyClass*, std::unique_ptr<MyClass>>
lugar de un conjunto. Luego puedes agregar elementos como este:
std::unique_ptr<MyClass> instance(new MyClass);
map.emplace(instance.get(), std::move(instance));
Si el objetivo es tiempo constante para la búsqueda, no creo que haya una solución. std::unordered_set<std::unique_ptr<MyClass>>::find
requiere un std::unique_ptr<MyClass>
como argumento. Tendrá que cambiar el contenedor o cambiar el tipo contenido.
Una posibilidad podría ser reemplazar std::unique_ptr
con std::shared_ptr
, y cambiar el resto del código para que todos los MyClass
se pongan en un shared_ptr tan pronto como se creen, y solo se manipulen a través de punteros compartidos. Lógicamente, esto es probablemente más coherente de todos modos: unique_ptr
implica bastante (por su nombre, así como su semántica) que no hay otros punteros al objeto. Por otro lado, es posible que no pueda utilizar shared_ptr, si, por ejemplo, MyClass
tiene punteros a otros MyClass
, que pueden generar un ciclo.
De lo contrario, si puede aceptar el acceso O (lg n), en lugar del acceso constante (la diferencia generalmente no se nota hasta que las tablas son bastante grandes), puede usar un std::vector<MyClass>
, usando std::lower_bound
para mantenerlo ordenado. A diferencia de std::unordered_set<>::find
, std::lower_bound
no requiere que el valor objetivo tenga el mismo tipo que el value_type
de la secuencia; todo lo que tiene que hacer es asegurarse de que sean comparables, por ejemplo, al proporcionar un objeto Compare
en la línea de:
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 );
}
};
La inserción puede implicar varios movimientos, pero mover std::unique_ptr
debería ser bastante barato, y la std::unique_ptr
mejorada de esta solución podría compensar los costos adicionales de tiempo de ejecución que impone.
También puedes usar un eliminador que opcionalmente no hace nada.
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));
Tenga en cuenta que la capacidad para realizar búsquedas heterogéneas en contenedores estándar está sujeta a algunas propuestas.
http://cplusplus.github.io/LWG/lwg-proposal-status.html listas
- N3465 Agregar búsqueda de comparación heterogénea a contenedores asociativos para TR2 (Rev 2) [Manejar con N3573]
- N2882 id.
- N3573 Extensiones heterogéneas para contenedores desordenados [Manejar con N3465]
Especialmente este último parece que cubriría su caso de uso.
Por ahora, aquí hay una IMO no muy bonita pero trabajando una solución alternativa (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;
}
Advertencia También es muy ineficiente, por supuesto.