c++ pointers cpp-core-guidelines guideline-support-library

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 para array_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 ? -
  • Rebindable? - No
  • Se puede utilizar en lugar de algo que no sean punteros? - No

std::reference_wrapper<T> :

  • No se puede almacenar nullptr ? -
  • Rebindable? -
  • Se puede utilizar en lugar de algo que no sean punteros? - No

gsl::not_null<T*> :

  • No se puede almacenar nullptr ? -
  • Rebindable? -
  • Se puede utilizar en lugar de algo que no sean punteros? -

Ahora aquí están las preguntas, finalmente:

  1. ¿Es correcto mi entendimiento de las diferencias entre estos conceptos?
  2. ¿ std::reference_wrapper significa que std::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.