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_null
no es solo para punteros incorporados. Funciona paraarray_view
,string_view
,unique_ptr
,shared_ptr
y 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_wrapper
significa questd::reference_wrapper
ahora 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.