unique_ptr smart shared_ptr make_unique c++ c++11 casting shared-ptr unique-ptr

c++ - smart - make_unique



Alternativas de static_pointer_cast para unique_ptr (3)

Entiendo que usar static_pointer_cast con unique_ptr conduciría a una propiedad compartida de los datos contenidos.

Solo si lo defines mal. La solución obvia sería transferir la propiedad, de modo que el objeto de origen termine vacío.

Si no desea transferir la propiedad, simplemente use un puntero sin formato.

O si quiere dos propietarios, use shared_ptr .

Parece que su pregunta es solo en parte acerca de la operación de lanzamiento real, y en parte simplemente la falta de una política de propiedad clara para el puntero. Si necesita varios propietarios, si ambos usan el mismo tipo, o si uno se convierte a un tipo diferente, entonces no debe usar unique_ptr .

De todos modos, hacer eso resulta en dos unique_ptr que nunca deberían existir al mismo tiempo, por lo que está simplemente prohibido.
Correcto, tiene sentido, absolutamente, es por eso que no existe nada como static_unique_pointer_cast de hecho.

No, no es por eso que no existe. No existe porque es trivial escribirlo usted mismo, si lo necesita (y siempre que le dé una semántica sensata de propiedad única). Solo saca el puntero con release() y luego ponlo en otro unique_ptr . Simple y seguro

Ese no es el caso para shared_ptr , donde la solución "obvia" no hace lo correcto:

shared_ptr<Derived> p2(static_cast<Derived*>(p1.get());

Eso crearía dos objetos shared_ptr diferentes que posean el mismo puntero, pero no compartan la propiedad (es decir, ambos intentarían eliminarlo, causando un comportamiento indefinido).

Cuando shared_ptr se estandarizó por primera vez no había una manera segura de hacerlo, por lo que se static_pointer_cast y las funciones de conversión relacionadas. Necesitaban acceso a los detalles de implementación de la shared_ptr contabilidad shared_ptr para funcionar.

Sin embargo, durante el proceso de estandarización C ++ 11, shared_ptr se mejoró mediante la adición del "constructor de aliasing" que le permite realizar el reparto de manera simple y segura:

shared_ptr<Derived> p2(p1, static_cast<Derived*>(p1.get());

Si esta característica siempre ha sido parte de shared_ptr entonces es posible, tal vez incluso probable, que static_pointer_cast nunca se haya definido.

Entiendo que usar static_pointer_cast con unique_ptr conduciría a una propiedad compartida de los datos contenidos.
En otros términos, lo que me gustaría hacer es:

unique_ptr<Base> foo = fooFactory(); // do something for a while unique_ptr<Derived> bar = static_unique_pointer_cast<Derived>(foo);

De todos modos, hacer eso resulta en dos unique_ptr que nunca deberían existir al mismo tiempo, por lo que está simplemente prohibido.
Correcto, tiene sentido, absolutamente, es por eso que no existe nada como static_unique_pointer_cast hecho.

Hasta ahora, en los casos en que quiero almacenar punteros a esas clases base, pero también necesito echarlos a algunas clases derivadas (como un ejemplo, imagine un escenario que implica borrado de tipo), he usado shared_ptr s por lo que ya he mencionado.

De todos modos, estaba adivinando si hay alternativas a shared_ptr s para ese problema o si realmente son la mejor solución en ese caso.


Punteros crudos

La solución para su problema es obtener el puntero bruto (no propietario) y convertirlo; luego, simplemente deje que el puntero sin unique_ptr<Base> salga del alcance y deje que el unique_ptr<Base> constelación conserve la vida útil del objeto propiedad.

Me gusta esto:

unique_ptr<Base> foo = fooFactory(); { Base* tempBase = foo.get(); Derived* tempDerived = static_cast<Derived*>(tempBase); } //tempBase and tempDerived go out of scope here, but foo remains -> no need to delete

Unique_pointer_cast

La otra opción es usar la función release() de unique_ptr para envolverlo en otro unique_ptr.

Me gusta esto

template<typename TO, typename FROM> unique_ptr<TO> static_unique_pointer_cast (unique_ptr<FROM>&& old){ return unique_ptr<TO>{static_cast<TO*>(old.release())}; //conversion: unique_ptr<FROM>->FROM*->TO*->unique_ptr<TO> } unique_ptr<Base> foo = fooFactory(); unique_ptr<Derived> foo2 = static_unique_pointer_cast<Derived>(std::move(foo));

Recuerde que esto invalida el viejo puntero foo

Referencia de punteros sin procesar

Solo para completar la respuesta, esta solución fue en realidad propuesta como una pequeña modificación de los punteros sin procesar por el OP en los comentarios.

Al igual que con el uso de punteros sin procesar, uno puede convertir los punteros sin procesar y luego crear una referencia a partir de ellos al cancelar la suscripción. En este caso, es importante garantizar que la vida útil de la referencia creada no exceda la duración de la unique_ptr.

Muestra:

unique_ptr<Base> foo = fooFactory(); Derived& bar = *(static_cast<Derived*>(foo.get())); //do not use bar after foo goes out of scope


Me gustaría agregar algo a la respuesta anterior de Anedar que llama al método de miembro de la release() de std::unique_ptr< U > dado. Si también se desea implementar un dynamic_pointer_cast (además de un static_pointer_cast ) para convertir std::unique_ptr< U > a std::unique_ptr< T > , se debe asegurar que el recurso guardado por el único puntero se libere adecuadamente en caso de que el dynamic_cast falla (es decir, devuelve un nullptr ). De lo contrario, se produce una pérdida de memoria.

Código :

#include <iostream> #include <memory> template< typename T, typename U > inline std::unique_ptr< T > dynamic_pointer_cast(std::unique_ptr< U > &&ptr) { U * const stored_ptr = ptr.release(); T * const converted_stored_ptr = dynamic_cast< T * >(stored_ptr); if (converted_stored_ptr) { std::cout << "Cast did succeeded/n"; return std::unique_ptr< T >(converted_stored_ptr); } else { std::cout << "Cast did not succeeded/n"; ptr.reset(stored_ptr); return std::unique_ptr< T >(); } } struct A { virtual ~A() = default; }; struct B : A { virtual ~B() { std::cout << "B::~B/n"; } }; struct C : A { virtual ~C() { std::cout << "C::~C/n"; } }; struct D { virtual ~D() { std::cout << "D::~D/n"; } }; int main() { std::unique_ptr< A > b(new B); std::unique_ptr< A > c(new C); std::unique_ptr< D > d(new D); std::unique_ptr< B > b1 = dynamic_pointer_cast< B, A >(std::move(b)); std::unique_ptr< B > b2 = dynamic_pointer_cast< B, A >(std::move(c)); std::unique_ptr< B > b3 = dynamic_pointer_cast< B, D >(std::move(d)); }

Salida (posible ordenamiento) :

Cast did succeeded Cast did not succeeded Cast did not succeeded B::~B D::~D C::~C

Los destructores de C y D no serán llamados, si uno usa:

template< typename T, typename U > inline std::unique_ptr< T > dynamic_pointer_cast(std::unique_ptr< U > &&ptr) { return std::unique_ptr< T >(dynamic_cast< T * >(ptr.release())); }