He estado escribiendo C ++ durante muchos años, usando nullptr
para punteros nulos. También conozco C, de donde se origina NULL, y recuerdo que es la constante para un puntero nulo, con tipo void *
.
Por razones, tuve que usar NULL
en mi código C++ para algo. Bueno, imagina mi sorpresa cuando, durante la deducción de un argumento de plantilla, el compilador me dice que mi NULL es realmente un... largo. Entonces, verifiqué dos veces:
#include <type_traits> #include <cstddef> static_assert(not std::is_same<decltype(NULL), long>::value, "NULL is long ???");
Y, de hecho, la aserción estática falla (con GCC y con Clang).
Revisé en cppreference.com , y efectivamente (redacción C++ 11):
La macro NULL es una constante de puntero nulo definida por la implementación, que puede ser un literal entero con valor cero o un prvalue de tipo
std::nullptr_t
.
¿Por qué esto tiene sentido? En sí mismo, ya la luz de la incompatibilidad de C?
En C, un void*
se puede convertir implícitamente en cualquier T*
. Como tal, hacer que NULL
sea un void*
es completamente apropiado.
Pero eso es profundamente peligroso. Entonces, C ++ eliminó tales conversiones, lo que requiere que realice la mayoría de las conversiones de puntero manualmente. Pero eso crearía una fuente de incompatibilidad con C; un programa válido en C que usara NULL
de la forma en que C quería no se compilaría en C++. También requeriría un montón de redundancia: T *pt = (T*)(NULL);
, lo que sería irritante y sin sentido.
Entonces, C ++ redefinió la macro NULL
para que sea el literal entero 0. En C, el 0 literal también se puede convertir implícitamente en cualquier tipo de puntero y genera un valor de puntero nulo, comportamiento que C ++ mantuvo.
Ahora, por supuesto, usar el literal 0 (o más exactamente, una expresión constante entera cuyo valor es 0) para una constante de puntero nulo... no fue la mejor idea. Particularmente en un lenguaje que permite la sobrecarga. Entonces, C ++ 11 optó por usar NULL por completo sobre una palabra clave que significa específicamente "constante de puntero nulo" y nada más.
En ambos idiomas, NULL
es una constante de puntero nulo definida por la implementación . C++ sección 17.2.3, que hace referencia a C 7.19 para la definición.
Eso significa que en C, NULL
se puede definir como un tipo entero o como un tipo void*
, y en C++ se puede definir como un tipo entero; también podría definirse como nullptr_t
si he interpretado el estándar correctamente. Una nota al pie oculta (no normativa) dice que (void*)0
no puede ser una definición válida, y eso tiene sentido porque C++ no tiene la conversión implícita de C de void*
a otros tipos de punteros.
Si tiene un compilador de C que lo define como ((void*)0)
y un compilador de C++ que lo define como 0L
, ambos son conformes. Ambas implementaciones satisfacen los casos de uso (pueden asignar y comparar con NULL
).
C ++ introdujo nullptr
principalmente para dar un valor escrito adecuado para la sobrecarga, ya que se consideró sorprendente que pasar NULL
a una función pudiera seleccionar una versión entera.
Mi suposición siempre ha sido que las decisiones de las implementaciones de mantener NULL
como 0
era una cuestión de compatibilidad con versiones anteriores de C++98. He visto mucho código usando NULL
en contextos que no son punteros, particularmente como un sustituto (probablemente no intencional, o al menos desinformado) para un terminador nulo en una matriz. Dado que la mejor práctica desde 2011 ha sido "no usar NULL
en absoluto", redefinir NULL
a nullptr
potencialmente rompería el código malo/antiguo, mientras que hace muy poco para ayudar al código bueno/nuevo.
C no define simplemente NULL
como (void *)0
. También es posible definirlo como 0
o cualquier expresión constante que lo evalúe. El uso de #define NULL 0
funciona en cualquier idioma.
Editar: esta respuesta es muy buena y más detallada.
En
vcruntime.h
definir como sigue:
#ifdef __cplusplus #define NULL 0 #else #define NULL ((void *)0)