¿Por qué se deben declarar las funciones antes de que se utilicen?

Al leer algunas respuestas a esta pregunta , comencé a preguntarme por qué el comstackdor realmente necesita saber acerca de una función cuando la encuentra por primera vez. ¿No sería simple simplemente agregar un pase adicional al analizar una unidad de comstackción que recostack todos los símbolos declarados en el interior, de modo que el orden en el que se declaran y utilizan ya no importa?

Uno podría argumentar, que declarar funciones antes de que se usen ciertamente es un buen estilo, pero me pregunto, ¿hay alguna otra razón por la cual esto es obligatorio en C ++?

Editar: un ejemplo para ilustrar: supongamos que tiene funciones definidas en línea en un archivo de encabezado. Estas dos funciones se llaman entre sí (tal vez un cruce de árbol recursivo, donde las capas impares y pares del árbol se manejan de forma diferente). La única forma de resolver esto sería hacer una statement directa de una de las funciones antes que la otra.

Un ejemplo más común (aunque con clases, no funciones) es el caso de clases con constructores private y fábricas. La fábrica necesita conocer la clase para crear instancias de la misma, y ​​la clase necesita conocer la fábrica para la statement del friend .

Si este es un requisito de los viejos tiempos, ¿por qué no se eliminó en algún momento? No rompería el código existente, ¿verdad?

¿Cómo propone resolver los identificadores no declarados que se definen en una unidad de traducción diferente ?

C ++ no tiene un concepto de módulo, pero tiene una traducción separada como herencia de C. Un comstackdor de C ++ comstackrá cada unidad de traducción por sí mismo, sin saber nada sobre otras unidades de traducción. (Excepto que la export rompió esto, que es probablemente la razón por la que, lamentablemente, nunca despegó).
Los archivos de encabezado , que es donde generalmente se colocan las declaraciones de los identificadores que se definen en otras unidades de traducción, en realidad son solo una forma muy torpe de deslizar las mismas declaraciones en diferentes unidades de traducción. No harán que el comstackdor sepa que existen otras unidades de traducción con identificadores definidos en ellas.

Edite sus ejemplos adicionales:
Con toda la inclusión textual en lugar de un concepto de módulo adecuado, la comstackción ya lleva mucho tiempo para C ++, por lo que requerir otro pase de comstackción (donde la comstackción ya está dividida en varias pasadas, no todas optimizadas y fusionadas, IIRC) empeoraría una ya es un mal problema. Y cambiar esto probablemente alteraría la resolución de sobrecarga en algunos escenarios y así rompería el código existente.

Tenga en cuenta que C ++ sí requiere un pase adicional para analizar las definiciones de clases, ya que las funciones miembro definidas en línea en la definición de clase se analizan como si estuvieran definidas justo detrás de la definición de clase. Sin embargo, esto se decidió cuando se pensó en C con clases, por lo que no había una base de código existente para romper.

Porque C y C ++ son idiomas antiguos . Los primeros comstackdores no tenían mucha memoria, por lo que estos lenguajes fueron diseñados para que un comstackdor pueda leer el archivo de arriba a abajo, sin tener que considerar el archivo como un todo .

Históricamente, C89 te dejó hacer esto. La primera vez que el comstackdor vio el uso de una función y no tenía un prototipo predefinido, “creó” un prototipo que coincidía con el uso de la función.

Cuando C ++ decidió agregar estricta verificación de tipos al comstackdor, se decidió que ahora se necesitaban prototipos. Además, C ++ heredó la comstackción de un solo pase de C, por lo que no pudo agregar un segundo pase para resolver todos los símbolos.

Pienso en dos razones:

  • Hace que el análisis sea fácil. No se necesita pase adicional.
  • También define el scope ; los símbolos / nombres están disponibles solo después de su statement. Significa, si declaro una variable global int g_count; , el código posterior después de esta línea puede usarlo, ¡pero no el código antes de la línea! El mismo argumento para las funciones globales.

Como ejemplo, considere este código:

 void g(double) { cout << "void g(double)" << endl; } void f() { g(int());//this calls g(double) - because that is what is visible here } void g(int) { cout << "void g(int)" << endl; } int main() { f(); g(int());//calls g(int) - because that is what is the best match! } 

Salida:

void g (doble)
void g (int)

Vea la salida en ideone: http://www.ideone.com/EsK4A

La razón principal será hacer que el proceso de comstackción sea lo más eficiente posible. Si agrega un pase adicional, agrega tiempo y espacio de almacenamiento. Recuerde que C ++ se desarrolló antes de la época de los procesadores Quad Core 🙂

El lenguaje de progtwigción C fue diseñado para que el comstackdor pueda ser implementado como un comstackdor de un solo paso . En dicho comstackdor, cada fase de comstackción solo se ejecuta una vez. En dicho comstackdor, no puede hacer referencia a una entidad que se define más adelante en el archivo fuente.

Además, en C, el comstackdor solo interpreta una sola unidad de comstackción (generalmente un archivo .c y todos los archivos .h incluidos) a la vez. Por lo tanto, necesitaba un mecanismo para derivar a una función definida en otra unidad de comstackción.

Se tomó la decisión de permitir el comstackdor de una sola pasada y de poder dividir un proyecto en una pequeña unidad de comstackción porque en ese momento la memoria y la potencia de procesamiento disponibles eran muy ajustadas. Y permitir la reenvío de statement podría resolver fácilmente el problema con una sola característica.

El lenguaje C ++ se derivó de C y heredó la característica de él (ya que quería ser lo más compatible posible con C para facilitar la transición).

Supongo que porque C es bastante viejo y en el momento en que se diseñó C, la comstackción eficiente era un problema porque las CPU eran mucho más lentas.

Como C ++ es un lenguaje estático, el comstackdor necesita verificar si el tipo de valores es compatible con el tipo esperado en los parámetros de la función. Por supuesto, si no conoce la firma de la función, no puede hacer este tipo de comprobaciones, desafiando así el propósito de un comstackdor estático. Pero, como tiene una insignia plateada en C ++, creo que ya lo sabe.

Las especificaciones del lenguaje C ++ se hicieron correctamente porque el diseñador no quería forzar un comstackdor de múltiples pasos, cuando el hardware no era tan rápido como el que está disponible en la actualidad. Al final, creo que, si C ++ se diseñó hoy, esta imposición desaparecería, pero luego, tendríamos otro idioma :-).

Una de las principales razones por las que esto se hizo obligatorio incluso en C99 (en comparación con C89, donde podría haber funciones declaradas implícitamente) es que las declaraciones implícitas son muy propensas a errores. Considera el siguiente código:

Primer archivo:

 #include  void doSomething(double x, double y) { printf("%g %g\n",x,y); } 

Segundo archivo:

 int main() { doSomething(12345,67890); return 0; } 

Este progtwig es un progtwig sintácticamente válido * C89. Puede comstackrlo con GCC utilizando este comando (suponiendo que los archivos fuente se llaman test.c y test0.c ):

 gcc -std=c89 -pedantic-errors test.c test0.c -o test 

¿Por qué imprime algo extraño (al menos en linux-x86 y linux-amd64)? ¿Puedes detectar el problema en el código de un vistazo? Ahora intente reemplazar c89 con c99 en la línea de comandos, y el comstackdor le notificará inmediatamente sobre su error.

Lo mismo con C ++. Pero en C ++ hay otras razones importantes por las cuales se necesitan declaraciones de funciones, se discuten en otras respuestas.

* Pero tiene un comportamiento indefinido

Aún así, puede usar una función antes de que se declare a veces (para ser estricto en la redacción: “antes” es sobre el orden en que se lee la fuente del progtwig) – ¡dentro de una clase !:

 class A { public: static void foo(void) { bar(); } private: static void bar(void) { return; } }; int main() { A::foo(); return 0; } 

(Cambiar la clase a un espacio de nombres no funciona, según mis pruebas).

Probablemente se deba a que el comstackdor coloca las definiciones de funciones de miembros dentro de la clase justo después de la statement de clase, como alguien lo señaló aquí en las respuestas.

El mismo enfoque podría aplicarse a todo el archivo fuente: primero, descarte todo menos la statement, luego maneje todo lo pospuesto. (Ya sea un comstackdor de dos pasos o una memoria lo suficientemente grande como para contener el código fuente pospuesto).

¡Jaja! Entonces, pensaron que un archivo fuente completo sería demasiado grande para guardarlo en la memoria, pero una sola clase con definiciones de funciones no lo haría : pueden permitir que toda una clase permanezca en la memoria y espere hasta que la statement se filtre ( o haz un segundo pase para el código fuente de clases)!

Recuerdo que con Unix y Linux, tienes Global y Local . En su propio entorno, local funciona para funciones, pero no funciona para Global(system) . Debes declarar la función Global .