shared_ptr make_unique c++ c++11 unique-ptr dereference dangling-pointer

c++ - make_unique - ¿Por qué no hay una alternativa segura a unique_ptr:: operator*()?



c++ make_unique (6)

std::vector tiene la función miembro at() como una alternativa segura al operator[] , por lo que se aplica la verificación de límites y no se crean referencias colgantes:

void foo(std::vector<int> const&x) { const auto&a=x[0]; // What if x.empty()? Undefined behavior! const auto&a=x.at(0); // Throws exception if x.empty(). }

Sin embargo, std::unique_ptr carece de la funcionalidad correspondiente:

void foo(std::unique_ptr<int> const&x) { const auto&a=*x; // What if bool(x)==false? Undefined behavior! }

Sería genial si std::unique_ptr tuviera una alternativa segura, por ejemplo, el miembro ref() (y cref() ) que nunca devuelve una referencia pendiente, sino que lanza una excepción. Posible implementación:

template<typename T> typename add_lvalue_reference<T>::type unique_ptr<T>::ref() const noexcept(false) { if(bool(*this)==false) throw run_time_error("trying to de-refrence null unique_ptr"); return this->operator*(); }

¿Hay alguna buena razón por la cual el estándar no proporciona este tipo de cosas?


No puedo decir por qué el comité decidió no agregar un método seguro de desreferenciación, la respuesta es probablemente "porque no se propuso" o "porque un puntero no tiene uno" . Pero es trivial escribir una plantilla de función libre por su cuenta que toma cualquier puntero como un argumento, lo compara con nullptr y luego lanza una excepción o devuelve una referencia al objeto apuntado.

Si no lo elimina a través de un puntero a la clase base, debería ser posible derivar públicamente de un unique_ptr y simplemente agregar una función miembro.

Sin embargo, tenga en cuenta que el uso de este método comprobado en todas partes puede generar un impacto significativo en el rendimiento (igual que en). Usualmente, usted desea validar sus parámetros a lo más una vez, para lo cual una sola instrucción if al principio es mucho más adecuada.

También está la escuela que dice que no debe lanzar excepciones en respuesta a errores de programación. Tal vez el peopke a cargo del diseño unique_ptr pertenecía a esta escuela, mientras que la gente que diseñaba vectores (que es mucho más antigua) no lo era.


Siguiendo la sugerencia de MikeMB, aquí hay una posible implementación de una función gratuita para desreferenciar los punteros y unique_ptr s por igual.

template<typename T> inline T& dereference(T* ptr) noexcept(false) { if(!ptr) throw std::runtime_error("attempt to dereference a nullptr"); return *ptr; } template<typename T> inline T& dereference(std::unique_ptr<T> const& ptr) noexcept(false) { if(!ptr) throw std::runtime_error("attempt to dereference an empty unique_ptr)"); return *ptr; }


Sospecho que la respuesta real es simple, y la misma para muchos "¿Por qué no es así C ++?" preguntas:

Nadie lo propuso.

std::vector y std::unique_ptr no están diseñados por las mismas personas, al mismo tiempo, y no se usan de la misma manera, por lo que no necesariamente siguen los mismos principios de diseño.


Uno de los objetivos principales de un diseño de API de puntero inteligente es ser un reemplazo directo con valor agregado, sin efectos secundarios o efectos secundarios, y casi sin gastos generales. if (ptr) ptr->... es la forma en que se realiza el acceso seguro al puntero desnudo, la misma sintaxis funciona bien con los punteros inteligentes, por lo que no requiere un cambio de código cuando uno se reemplaza por el otro.

Una verificación adicional de la validez (por ejemplo, para lanzar una excepción) colocada dentro de un puntero interferiría con el predictor de rama y, por lo tanto, podría tener un efecto en el rendimiento, que ya no se puede considerar un reemplazo de costo cero.


Usted tiene

operator bool()

Ejemplo de: cplusplusreference

// example of unique_ptr::operator bool #include <iostream> #include <memory> int main () { std::unique_ptr<int> foo; std::unique_ptr<int> bar (new int(12)); if (foo) std::cout << "foo points to " << *foo << ''/n''; else std::cout << "foo is empty/n"; if (bar) std::cout << "bar points to " << *bar << ''/n''; else std::cout << "bar is empty/n"; return 0; }

unique_ptr es un envoltorio simple para un puntero en bruto, sin necesidad de lanzar una excepción cuando puede verificar fácilmente una condición booleana.

Edición: Aparentemente el operator* puede lanzar.

Las excepciones 1) pueden lanzarse, por ejemplo, si el puntero define un operador de lanzamiento *

Tal vez alguien podría encender algunas luces para definir un operador de lanzamiento *


unique_ptr se diseñó específicamente como una clase de puntero ligero con detección de estado nulo (por ejemplo, se indica en optional en una propuesta para agregar una clase de utilidad para representar objetos opcionales (Revisión 3) )

Dicho esto, la capacidad que está solicitando ya está en el lugar dado operator* documentación del operator* dice:

// may throw, e.g. if pointer defines a throwing operator* typename std::add_lvalue_reference<T>::type operator*() const;

El tipo de pointer se define como

std::remove_reference<Deleter>::type::pointer if that type exists, otherwise T*

Por lo tanto, a través de su eliminador personalizado, puede realizar cualquier operación sobre la marcha, incluida la comprobación de punteros nulos y el lanzamiento de excepciones.

#include <iostream> #include <memory> struct Foo { // object to manage Foo() { std::cout << "Foo ctor/n"; } Foo(const Foo&) { std::cout << "Foo copy ctor/n"; } Foo(Foo&&) { std::cout << "Foo move ctor/n"; } ~Foo() { std::cout << "~Foo dtor/n"; } }; struct Exception {}; struct InternalPtr { Foo *ptr = nullptr; InternalPtr(Foo *p) : ptr(p) {} InternalPtr() = default; Foo& operator*() const { std::cout << "Checking for a null pointer.." << std::endl; if(ptr == nullptr) throw Exception(); return *ptr; } bool operator != (Foo *p) { if(p != ptr) return false; else return true; } void cleanup() { if(ptr != nullptr) delete ptr; } }; struct D { // deleter using pointer = InternalPtr; D() {}; D(const D&) { std::cout << "D copy ctor/n"; } D(D&) { std::cout << "D non-const copy ctor/n";} D(D&&) { std::cout << "D move ctor /n"; } void operator()(InternalPtr& p) const { std::cout << "D is deleting a Foo/n"; p.cleanup(); }; }; int main() { std::unique_ptr<Foo, D> up(nullptr, D()); // deleter is moved try { auto& e = *up; } catch(Exception&) { std::cout << "null pointer exception detected" << std::endl; } }

Ejemplo vivo

Para completar, publicaré dos alternativas / soluciones adicionales:

  1. Comprobador de puntero para un unique_ptr través del operator bool

    #include <iostream> #include <memory> int main() { std::unique_ptr<int> ptr(new int(42)); if (ptr) std::cout << "before reset, ptr is: " << *ptr << ''/n''; ptr.reset(); if (ptr) std::cout << "after reset, ptr is: " << *ptr << ''/n''; }

    (Probablemente esta sea la forma más inteligente de lidiar con el problema)

  2. Una solución alternativa, aunque más complicada, es usar un tipo de envoltura que se encargue del manejo excepcional