2012-04-02 12 views
5

tengo curiosidad por qué C++ no define vacío a través de:¿Por qué no se anula el valor nulo en C++?

typedef struct { } void; 

es decir, ¿Cuál es el valor en un tipo que no se puede instanciar, incluso si esa instalación no debe producir ningún código?

Si utilizamos gcc -O3 -S, entonces tanto los siguientes productos idénticos ensamblador:

int main() { return 0; } 

y

template <class T> T f(T a) { } 
typedef struct { } moo; 
int main() { moo a; f(a); return 0; } 

Esto tiene mucho sentido. Un struct { } simplemente toma un valor vacío, lo suficientemente fácil como para optimizarlo. De hecho, la parte extraña es que producen código diferente sin el -O3.

No obstante, no se puede utilizar este mismo truco con simplemente typedef void moo porque nulo no puede asumir ningún valor, ni siquiera uno vacío. ¿Esta distinción tiene alguna utilidad?

Hay varios otros lenguajes fuertemente tipados como Haskell, y presumiblemente los ML, que tienen un valor para su tipo de vacío, pero no ofrecen tipos sin valor abiertamente, aunque algunos poseen tipos de punteros nativos, que actúan como void *.

Respuesta

4

veo la razón de void ser incapaz de crear una instancia que viene de las raíces C de C++. En los viejos días sangrientos, el tipo de seguridad no era tan importante y se pasaban constantemente void* s. Sin embargo, siempre podría estar buscando un código que no diga literalmente void* (debido a typedef s, macros y en C++, plantillas) pero aún lo significa.

Es un poco de seguridad si no puede desreferenciar un void* pero primero tiene que convertirlo en un puntero al tipo correcto. Si lo logra utilizando un tipo incompleto como Ben Voigt sugiere, o si usa el tipo incorporado void no importa. Estás protegido incorrectamente adivinando que estás tratando con un tipo, que no eres.

Sí, void presenta casos especiales molestos, especialmente al diseñar plantillas. Pero es una buena cosa (es decir, intencional) que el compilador no acepte silenciosamente los intentos de instanciar void.

+0

Estoy de acuerdo con casi todo, excepto el último párrafo. ¿Los casos especiales molestos que presenta al tratar con plantillas son intencionales? ¿Para qué? Para molestar a los programadores? –

+0

@ R.MartinhoFernandes: Bueno, no es intencional que nos moleste, pero es intencional en el sentido de que su biblioteca debería ser capaz de tratar con tipos que * no puede instanciar *, 'void' es el ejemplo más destacado. Por ejemplo, si escribiste una plantilla 'memcpy' de tipo seguro que básicamente hace esto:' * to = * from'. Luego, conectar 'void' * debería * hacer que el compilador te llame, no lo acepte silenciosamente, simplemente porque copiar el valor ficticio OPs para' void' ¡no hubiera sido lo que tenías en mente! – bitmask

+0

Oh, entendí mal entonces. –

3

Porque eso no haría void un tipo incompleto, ¿o sí?

probar su código de ejemplo de nuevo con:

struct x; // incomplete 
typedef x moo; 
+0

¿Es 'moo' un tipo incompleto en mi ejemplo? Funciona bien en los pocos lugares que lo he usado. –

+0

@Jeff: No, tu 'moo' es un tipo completo. El mío está incompleto. 'void' es un tipo incompleto que nunca se puede completar. –

+0

¿Cuál dirías que es la ventaja de que 'void' es un tipo incompleto? –

3

Por qué debería void ser un tipo incompleto?

Hay muchas razones.

Lo más simple es esto: moo FuncName(...) todavía debe devolver algo. Si es un valor neutral, si es el "valor no es un valor", o lo que sea; todavía debe decir return value;, donde value es un valor real. Debe decir return moo();.

¿Por qué escribir una función que devuelve algo técnicamente, cuando en realidad no está devolviendo algo? El compilador no puede optimizar el retorno porque devuelve un valor de tipo completo. La persona que llama podría usarlo.

C++ no son todas las plantillas, ya sabes. El compilador no necesariamente tiene el conocimiento para tirar todo. A menudo tiene que realizar optimizaciones en función que no tienen conocimiento del uso externo de ese código.

void en este caso significa "No devuelvo nada". Hay una diferencia fundamental entre eso y "Devuelvo un valor sin sentido". Es legal hacer esto:

moo FuncName(...) { return moo(); } 

moo x = FuncName(...); 

Esto es en el mejor de los casos engañoso. Una exploración superficial sugiere que se está devolviendo y almacenando un valor. El identificador x ahora tiene un valor y se puede usar para algo.

Considerando que la presente:

void FuncName(...) {} 

void x = FuncName(...); 

es un error de compilación. Así es la siguiente:

void FuncName(...) {} 

moo x = FuncName(...); 

Está claro lo que está pasando: FuncName no devuelve ningún valor.

Incluso si estuviera diseñando C++ desde cero, sin ninguna retención en off de C, que habría todavía necesita un poco de palabra clave para indicar la diferencia entre una función que devuelve un valor y una que no lo hace.

Además, void* es especial en parte porque void no es un tipo completo. Dado que el estándar exige que no sea un tipo completo, el concepto de void* puede significar "apuntador a la memoria sin tipo". Esto tiene una semántica de casting especializada. Los punteros a la memoria mecanografiada se pueden convertir implícitamente en memoria sin tipo. Los punteros a la memoria sin tipo se pueden convertir explícitamente al puntero tipeado que solía ser.

En su lugar, si usó moo*, la semántica se vuelve rara. Tiene un puntero a tipeado de memoria. Actualmente, C++ define la conversión entre punteros tipeados no relacionados (a excepción de ciertos casos especiales) como un comportamiento indefinido. Ahora el estándar tiene que agregar otra excepción para el tipo moo. Se tiene que decir lo que sucede cuando se hace esto:

moo *m = new moo; 
*moo; 

Con void, se trata de un error de compilación. ¿Qué pasa con moo? Todavía es inútil y sin sentido, pero el compilador tiene que permitirlo.

Para ser honesto, yo diría que la mejor pregunta sería "¿Por qué debería void ser un tipo completo?"

+0

Hay muchas situaciones en las que desea especializar el parámetro de tipo polimórfico a nada porque hacerlo tiene sentido lógicamente, hace que el código sea más legible, mejora la reutilización de código, etc. No me sorprende que no pueda convertir un 'vacío' a un 'moo', pero esto nunca surgiría en la práctica. –

+0

Para agregar algunas bonificaciones adicionales: 'throw moo()', 'moo [5]' - todas las cosas que no funcionan con 'void' _ porque están incompletas. – MSalters

+0

@JeffBurdges: Entonces puedes crear tu propia clase de "nada" vacía y hacer lo que necesites. –

Cuestiones relacionadas