Este código conceptual hace lo mismo para los tres punteros (inicialización segura del puntero):
int* p1 = nullptr;
int* p2 = NULL;
int* p3 = 0;
Entonces, ¿cuáles son las ventajas de asignar punteros nullptr
sobre la asignación de los valores NULL
o 0
?
int
yvoid *
no elegirá laint
versión sobre lavoid *
versión cuando se usanullptr
.f(nullptr)
es diferente def(NULL)
. Pero en lo que respecta al código anterior (asignación a una variable local), los tres punteros son exactamente iguales. La única ventaja es la legibilidad del código.nullptr
esa es la diferencia entre 0 yNULL
Respuestas:
En ese código, no parece haber una ventaja. Pero considere las siguientes funciones sobrecargadas:
¿Qué función se llamará? Por supuesto, la intención aquí es llamar
f(char const *)
, ¡pero en realidadf(int)
se llamará! Ese es un gran problema 1 , ¿no?Entonces, la solución a tales problemas es usar
nullptr
:Por supuesto, esa no es la única ventaja de
nullptr
. Aquí está otro:Como en la plantilla, el tipo de
nullptr
se deduce comonullptr_t
, por lo que puede escribir esto:1. En C ++,
NULL
se define como#define NULL 0
, por lo que es básicamenteint
, por esof(int)
se llama.fuente
nullptr
? (No. No soy exigente)NULL
el estándar requiere que tenga un tipo integral, y es por eso que generalmente se define como0
o0L
. Además, no estoy seguro de que me guste esanullptr_t
sobrecarga, ya que solo captura llamadas connullptr
, no con un puntero nulo de un tipo diferente, como(void*)0
. Pero puedo creer que tiene algunos usos, incluso si todo lo que hace es ahorrarte definiendo un tipo de marcador de posición de un solo valor para que signifique "ninguno".nullptr
tiene un valor numérico bien definido, mientras que las constantes de puntero nulo no. Una constante de puntero nulo se convierte en el puntero nulo de ese tipo (sea lo que sea). Se requiere que dos punteros nulos del mismo tipo se comparen de manera idéntica, y la conversión booleana convierte un puntero nulo enfalse
. No se requiere nada más. Por lo tanto, es posible que un compilador (tonto, pero posible) utilice, por ejemplo,0xabcdef1234
o algún otro número para el puntero nulo. Por otro lado,nullptr
se requiere convertir a cero numérico.f(nullptr)
no llamará a la función prevista? Hubo más de una motivación. Los programadores pueden descubrir muchas otras cosas útiles en los próximos años. Por lo tanto, no puede decir que solo hay un uso verdadero denullptr
.C ++ 11 introduce
nullptr
, se conoce como laNull
constante de puntero y mejora la seguridad de tipo y resuelve situaciones ambiguas a diferencia de la constante de puntero nulo dependiente de la implementación existenteNULL
. Para poder comprender las ventajas denullptr
. primero tenemos que entender qué esNULL
y cuáles son los problemas asociados con él.¿Qué es
NULL
exactamente?Pre C ++ 11
NULL
se usó para representar un puntero que no tiene valor o un puntero que no apunta a nada válido. Contrariamente a la noción popular,NULL
no es una palabra clave en C ++ . Es un identificador definido en los encabezados de la biblioteca estándar. En resumen, no puede usarNULL
sin incluir algunos encabezados de biblioteca estándar. Considere el programa de muestra :Salida:
El estándar C ++ define NULL como una macro definida por la implementación definida en ciertos archivos de encabezado de biblioteca estándar. El origen de NULL es de C y C ++ lo heredó de C. El estándar C definió NULL como
0
o(void *)0
. Pero en C ++ hay una sutil diferencia.C ++ no pudo aceptar esta especificación tal como es. A diferencia de C, C ++ es un lenguaje fuertemente tipado (C no requiere conversión explícita de
void*
ningún tipo, mientras que C ++ exige una conversión explícita). Esto hace que la definición de NULL especificada por el estándar C sea inútil en muchas expresiones C ++. Por ejemplo:Si NULL se definió como
(void *)0
, ninguna de las expresiones anteriores funcionaría.void *
astd::string
.void *
se necesita la función de conversión de puntero a miembro.Entonces, a diferencia de C, C ++ Standard tiene el mandato de definir NULL como literal numérico
0
o0L
.Entonces, ¿cuál es la necesidad de otro puntero nulo constante cuando ya lo tenemos
NULL
?Aunque el comité de estándares de C ++ propuso una definición NULL que funcionará para C ++, esta definición tenía su propia cuota de problemas. NULL funcionó lo suficientemente bien para casi todos los escenarios, pero no para todos. Dio resultados sorprendentes y erróneos para ciertos escenarios raros. Por ejemplo :
Salida:
Claramente, la intención parece ser llamar a la versión que toma
char*
como argumento, pero a medida que el resultado muestra la función que toma unaint
versión se llama. Esto se debe a que NULL es un literal numérico.Además, dado que está definido por la implementación si NULL es 0 o 0L, puede haber mucha confusión en la resolución de sobrecarga de funciones.
Programa de muestra:
Analizando el fragmento anterior:
doSomething(char *)
como se esperaba.doSomething(int)
pero tal vezchar*
se deseaba la versión porque0
también es un puntero nulo.NULL
se define como0
, llamadoSomething(int)
cuando tal vezdoSomething(char *)
fue intencionado, lo que tal vez resulte en un error lógico en tiempo de ejecución. SiNULL
se define como0L
, la llamada es ambigua y genera un error de compilación.Entonces, dependiendo de la implementación, el mismo código puede dar varios resultados, lo cual es claramente indeseable. Naturalmente, el comité de estándares de C ++ quería corregir esto y esa es la principal motivación para nullptr.
Entonces, ¿qué es
nullptr
y cómo evita los problemasNULL
?C ++ 11 introduce una nueva palabra clave
nullptr
para servir como puntero nulo constante. A diferencia de NULL, su comportamiento no está definido por la implementación. No es una macro pero tiene su propio tipo. nullptr tiene el tipostd::nullptr_t
. C ++ 11 define adecuadamente las propiedades de nullptr para evitar las desventajas de NULL. Para resumir sus propiedades:Propiedad 1: tiene su propio tipo
std::nullptr_t
, yPropiedad 2: es implícitamente convertible y comparable a cualquier tipo de puntero o tipo puntero a miembro, pero
Propiedad 3: no es implícitamente convertible o comparable a los tipos integrales, a excepción de
bool
.Considere el siguiente ejemplo:
En el programa anterior,
char *
Versión de llamadas , Propiedad 2 y 3Por lo tanto, la introducción de nullptr evita todos los problemas del buen viejo NULL.
¿Cómo y dónde debes usar
nullptr
?La regla general para C ++ 11 es simplemente comenzar a usar
nullptr
siempre que de otro modo hubiera usado NULL en el pasado.Referencias estándar:
C ++ 11 Estándar: C.3.2.4 Macro NULL
C ++ 11 Estándar: 18.2 Tipos
C ++ 11 Estándar: 4.10 Conversiones de puntero
C99 Estándar: 6.3.2.3 Punteros
fuente
nullptr
, aunque no sabía qué diferencia realmente tiene para mi código. Gracias por la gran respuesta y especialmente por el esfuerzo. Me trajo mucha luz sobre el tema.0xccccc....
, pero, una variable sin valor es una contradicción inherente.bool flag = nullptr;
). No, no está bien, aparece el siguiente error en el momento de la compilación con g ++ 6:error: converting to ‘bool’ from ‘std::nullptr_t’ requires direct-initialization [-fpermissive]
La verdadera motivación aquí es el reenvío perfecto .
Considerar:
En pocas palabras, 0 es un valor especial , pero los valores no pueden propagarse a través del sistema, solo los tipos pueden. Las funciones de reenvío son esenciales y 0 no puede ocuparse de ellas. Por lo tanto, era absolutamente necesario introducir
nullptr
, donde el tipo es lo que es especial, y el tipo de hecho puede propagarse. De hecho, el equipo de MSVC tuvo que presentarnullptr
antes de lo programado después de que implementaron referencias de valor y luego descubrieron esta trampa por sí mismos.Hay algunos otros casos de esquina en los que
nullptr
puede facilitar la vida, pero no es un caso central, ya que un elenco puede resolver estos problemas. ConsiderarLlama a dos sobrecargas separadas. Además, considere
Esto es ambiguo. Pero, con nullptr, puede proporcionar
fuente
forward((int*)0)
trabajos. ¿Me estoy perdiendo de algo?Conceptos básicos de nullptr
std::nullptr_t
es el tipo del puntero nulo literal, nullptr. Es un valor / valor de tipostd::nullptr_t
. Existen conversiones implícitas de nullptr a valor de puntero nulo de cualquier tipo de puntero.El 0 literal es un int, no un puntero. Si C ++ se encuentra mirando a 0 en un contexto donde solo se puede usar un puntero, interpretará a regañadientes 0 como un puntero nulo, pero esa es una posición alternativa. La política principal de C ++ es que 0 es un int, no un puntero.
Ventaja 1: elimine la ambigüedad al sobrecargar el puntero y los tipos integrales
En C ++ 98, la implicación principal de esto fue que la sobrecarga en punteros y tipos integrales podría generar sorpresas. Pasar 0 o NULL a tales sobrecargas nunca se llamó sobrecarga de puntero:
Lo interesante de esa llamada es la contradicción entre el significado aparente del código fuente ("Estoy llamando divertido con NULL-el puntero nulo") y su significado real ("Estoy llamando divertido con algún tipo de entero, no el nulo puntero").
La ventaja de nullptr es que no tiene un tipo integral. Llamar divertido a la función sobrecargada con nullptr llama a la sobrecarga void * (es decir, la sobrecarga del puntero), porque nullptr no puede verse como algo integral:
Usar nullptr en lugar de 0 o NULL evita las sorpresas de resolución de sobrecarga.
Otra ventaja de
nullptr
sobreNULL(0)
cuando se usa auto para el tipo de retornoPor ejemplo, suponga que encuentra esto en una base de código:
Si no sabe (o no puede descubrir fácilmente) qué devuelve findRecord, puede que no esté claro si el resultado es un tipo de puntero o un tipo integral. Después de todo, 0 (con qué resultado se prueba) podría ir en cualquier dirección. Si ve lo siguiente, por otro lado,
no hay ambigüedad: el resultado debe ser un tipo de puntero.
Ventaja 3
El programa anterior se compila y ejecuta con éxito, pero lockAndCallF1, lockAndCallF2 y lockAndCallF3 tienen código redundante. Es una pena escribir código como este si podemos escribir una plantilla para todo esto
lockAndCallF1, lockAndCallF2 & lockAndCallF3
. Por lo tanto, se puede generalizar con plantilla. He escrito la función de plantilla enlockAndCall
lugar de la definición múltiplelockAndCallF1, lockAndCallF2 & lockAndCallF3
para código redundante.El código se refactoriza de la siguiente manera:
Análisis detallado de por qué la compilación falló para
lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)
no paralockAndCall(f3, f3m, nullptr)
¿Por qué la compilación de
lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)
fallido?El problema es que cuando se pasa 0 a lockAndCall, la deducción de tipo de plantilla entra en acción para descubrir su tipo. El tipo de 0 es int, entonces ese es el tipo del parámetro ptr dentro de la instanciación de esta llamada a lockAndCall. Desafortunadamente, esto significa que en la llamada a func dentro de lockAndCall, se pasa un int, y eso no es compatible con el
std::shared_ptr<int>
parámetro quef1
espera. El 0 pasado en la llamada alockAndCall
estaba destinado a representar un puntero nulo, pero lo que realmente pasó fue int. Intentar pasar este int a f1 como astd::shared_ptr<int>
es un error de tipo. La llamada alockAndCall
con 0 falla porque dentro de la plantilla, se pasa un int a una función que requiere astd::shared_ptr<int>
.El análisis para la llamada que involucra
NULL
es esencialmente el mismo. CuandoNULL
se pasa alockAndCall
, se deduce un tipo integral para el parámetro ptr, y se produce un error de tipo cuando se pasa a un tipoptr
int o tipo intf2
, que espera obtener astd::unique_ptr<int>
.En contraste, la llamada que involucra
nullptr
no tiene problemas. Cuandonullptr
se pasa alockAndCall
, el tipo paraptr
se deduce que esstd::nullptr_t
. Cuandoptr
se pasa af3
, hay una conversión implícita destd::nullptr_t
aint*
, porque sestd::nullptr_t
convierte implícitamente a todos los tipos de puntero.Se recomienda, siempre que desee hacer referencia a un puntero nulo, use nullptr, no 0 o
NULL
.fuente
No hay una ventaja directa de tener
nullptr
en la forma en que ha mostrado los ejemplos.Pero considere una situación en la que tenga 2 funciones con el mismo nombre; 1 toma
int
y otro unint*
Si desea llamar
foo(int*)
pasando un NULL, entonces la forma es:nullptr
lo hace más fácil e intuitivo :Enlace adicional desde la página web de Bjarne.
Irrelevante pero en C ++ 11 nota al margen:
fuente
decltype(nullptr)
esstd::nullptr_t
.typedef decltype(nullptr) nullptr_t;
. Supongo que puedo mirar en el estándar. Ah, lo encontré: Nota: std :: nullptr_t es un tipo distinto que no es un tipo de puntero ni un puntero al tipo de miembro; más bien, un valor de este tipo es una constante de puntero nulo y puede convertirse en un valor de puntero nulo o un valor de puntero miembro nulo.nullptr
.Tal como otros ya han dicho, su principal ventaja radica en las sobrecargas. Y aunque las
int
sobrecargas explícitas frente a punteros pueden ser raras, considere las funciones de biblioteca estándar comostd::fill
(que me ha mordido más de una vez en C ++ 03):No se compila:
Cannot convert int to MyClass*
.fuente
OMI más importante que esos problemas de sobrecarga: en construcciones de plantillas profundamente anidadas, es difícil no perder de vista los tipos, y dar firmas explícitas es un gran esfuerzo. Por lo tanto, para todo lo que use, cuanto más enfocado con precisión al propósito previsto, mejor, reducirá la necesidad de firmas explícitas y permitirá que el compilador produzca mensajes de error más perspicaces cuando algo salga mal.
fuente