c++ - gsl:: not_null<T*> vs. std:: reference_wrapper<T> vs. T &
pointers cpp-core-guidelines (3)
Aquí hay un artículo útil que analiza gsl:not_null : https://visualstudiomagazine.com/articles/2016/06/01/using-the-not_null-template.aspx
Una parte relevante:
Muchos desarrolladores intentan usar referencias tanto como pueden porque las referencias no pueden ser nulas, pero no se puede volver a crear una referencia y hacer que se refiera a algo diferente, como se puede hacer con un puntero. Por lo tanto, hay veces en que los punteros son lo que debe usar, lo que lo lleva de regreso a toda esa comprobación nula y al temor recurrente de una excepción de puntero nulo.
C ++ Core Guidelines se ha presentado recientemente (¡felicitaciones!) Y me preocupa el tipo gsl::not_null . Como se indica en I.12: Declare un puntero que no debe ser nulo como not_null :
Para ayudar a evitar la eliminación de referencias de errores nullptr. Para mejorar el rendimiento al evitar las comprobaciones redundantes para nullptr.
...
Al establecer el intento en la fuente, los implementadores y las herramientas pueden proporcionar mejores diagnósticos, como encontrar algunas clases de errores a través del análisis estático y realizar optimizaciones, como eliminar las ramas y las pruebas nulas.
La intención es clara. Sin embargo, ya tenemos una función de idioma para eso. Los apuntadores que no pueden ser nulos se llaman referencias. Y aunque las referencias no pueden recuperarse una vez que se crean, este problema es resuelto por std::reference_wrapper .
La diferencia principal entre gsl::not_null y std::reference_wrapper veo en que esta última se puede usar solo en lugar de punteros, mientras que la primera funciona en cualquier elemento nullptr -asignable (cita de F.17: use a not_null para indicar que " null "no es un valor válido ):
not_nullno es solo para punteros incorporados. Funciona paraarray_view,string_view,unique_ptr,shared_ptry otros tipos de puntero.
Me imagino que la tabla de comparación de características es la siguiente:
T& :
- No se puede almacenar
nullptr? - Sí - Rebindable? - No
- Se puede utilizar en lugar de algo que no sean punteros? - No
std::reference_wrapper<T> :
- No se puede almacenar
nullptr? - Sí - Rebindable? - Sí
- Se puede utilizar en lugar de algo que no sean punteros? - No
gsl::not_null<T*> :
- No se puede almacenar
nullptr? - Sí - Rebindable? - Sí
- Se puede utilizar en lugar de algo que no sean punteros? - Sí
Ahora aquí están las preguntas, finalmente:
- ¿Es correcto mi entendimiento de las diferencias entre estos conceptos?
- ¿
std::reference_wrappersignifica questd::reference_wrapperahora es inútil?
PD : cpp-core-guidelines etiquetas cpp-core-guidelines y guideline-support-library para esto, espero que así sea.
Creo que todavía hay casos de uso para std::reference_wrapper que no están cubiertos por gsl::not_null . Básicamente, std::reference_wrapper duplica una referencia y tiene un operator T& conversión, mientras que not_null tiene una interfaz de puntero con operator-> . Un caso de uso que me viene a la mente de inmediato es cuando crea un hilo:
void funcWithReference(int& x) { x = 42; }
int i=0;
auto t = std::thread( funcWithReference, std::ref(i) );
Si no tengo control sobre funcWithReference , no puedo usar not_null .
Lo mismo se aplica a los funtores para los algoritmos, y tuve que usarlo también para atar las boost::signals .
Las referencias no son punteros que no pueden ser nulos. Las referencias son semánticamente muy diferentes a los punteros.
Las referencias tienen una asignación de valores y una semántica de comparación; es decir, las operaciones de asignación o comparación que implican referencias leen y escriben el valor al que se hace referencia. Los apuntadores tienen (contra intuición) asignación de referencia y semántica de comparación; es decir, las operaciones de asignación o comparación que implican punteros leen y escriben la referencia en sí (es decir, la dirección del objeto al que se hace referencia).
Como ha notado, las referencias no pueden ser rebotadas (debido a su semántica de asignación de valores), pero la plantilla de la clase reference_wrapper<T> puede recuperarse, porque tiene una semántica de asignación de referencia . Esto se debe a que reference_wrapper<T> está diseñado para ser utilizado con contenedores STL y algoritmos, y no se comportaría correctamente si su operador de asignación de copias no hiciera lo mismo que su constructor de copias. Sin embargo, reference_wrapper<T> todavía tiene una semántica de comparación de valores, como una referencia, por lo que se comporta de manera muy diferente a los punteros cuando se utiliza con contenedores STL y algoritmos. Por ejemplo, set<T*> puede contener punteros a diferentes objetos con el mismo valor, mientras que set<reference_wrapper<T>> puede contener una referencia a un solo objeto con un valor dado.
La plantilla de clase not_null<T*> tiene una asignación de referencia y una semántica de comparación, como un puntero; es un tipo de puntero. Esto significa que se comporta como un puntero cuando se utiliza con contenedores STL y algoritmos. Simplemente no puede ser nulo.
Entonces, tiene razón en su evaluación, excepto que olvidó la semántica de comparación. Y no, reference_wrapper<T> no se volverá obsoleto por ningún tipo de tipo de puntero, porque tiene una semántica de comparación de valores de referencia.