En: http://www.learncpp.com/cpp-tutorial/19-header-files/
Se menciona lo siguiente:
add.cpp:
int add(int x, int y)
{
return x + y;
}
main.cpp:
#include <iostream>
int add(int x, int y); // forward declaration using function prototype
int main()
{
using namespace std;
cout << "The sum of 3 and 4 is " << add(3, 4) << endl;
return 0;
}
Usamos una declaración directa para que el compilador supiera qué era "
add
" al compilarmain.cpp
. Como se mencionó anteriormente, escribir declaraciones de reenvío para cada función que desee usar que se encuentre en otro archivo puede volverse tedioso rápidamente.
¿Puedes explicar más la " declaración hacia adelante "? ¿Cuál es el problema si lo usamos en la main()
función?
c++
declaration
forward-declaration
Sencillez
fuente
fuente
Respuestas:
Por qué es necesaria la declaración directa en C ++
El compilador quiere asegurarse de que no haya cometido errores ortográficos o haya pasado un número incorrecto de argumentos a la función. Por lo tanto, insiste en que primero ve una declaración de 'agregar' (o cualquier otro tipo, clase o función) antes de usarlo.
Esto realmente solo permite que el compilador haga un mejor trabajo de validación del código, y le permite ordenar los cabos sueltos para que pueda producir un archivo de objeto ordenado. Si no tuviera que reenviar las declaraciones, el compilador produciría un archivo de objeto que tendría que contener información sobre todas las posibles suposiciones sobre cuál podría ser la función 'agregar'. Y el enlazador tendría que contener una lógica muy inteligente para tratar de determinar qué 'agregar' realmente pretendía llamar, cuando la función 'agregar' puede vivir en un archivo de objeto diferente, el enlazador se une con el que usa agregar para producir un dll o exe. Es posible que el enlazador obtenga el complemento incorrecto. Supongamos que desea usar int add (int a, float b), pero accidentalmente olvidó escribirlo, pero el vinculador encontró un int add ya existente (int a, int b) y pensé que era el correcto y usé eso en su lugar. Su código se compilaría, pero no estaría haciendo lo que esperaba.
Entonces, solo para mantener las cosas explícitas y evitar las conjeturas, etc., el compilador insiste en que declares todo antes de usarlo.
Diferencia entre declaración y definición
Por otro lado, es importante saber la diferencia entre una declaración y una definición. Una declaración solo proporciona suficiente código para mostrar cómo se ve algo, así que para una función, este es el tipo de retorno, la convención de llamada, el nombre del método, los argumentos y sus tipos. Pero el código para el método no es obligatorio. Para una definición, necesita la declaración y también el código para la función también.
Cómo las declaraciones directas pueden reducir significativamente los tiempos de compilación
Puede obtener la declaración de una función en su archivo actual .cpp o .h # incluyendo el encabezado que ya contiene una declaración de la función. Sin embargo, esto puede ralentizar su compilación, especialmente si #incluye un encabezado en un .h en lugar de .cpp de su programa, ya que todo lo que # incluye el .h que está escribiendo terminaría # incluyendo todos los encabezados escribiste #incluye también. De repente, el compilador tiene #incluidas páginas y páginas de código que necesita compilar incluso cuando solo desea utilizar una o dos funciones. Para evitar esto, puede usar una declaración directa y simplemente escribir la declaración de la función usted mismo en la parte superior del archivo. Si solo usa algunas funciones, esto realmente puede hacer que sus compilaciones sean más rápidas en comparación con #incluir siempre el encabezado. Para proyectos realmente grandes,
Romper referencias cíclicas donde dos definiciones se usan
Además, las declaraciones directas pueden ayudarlo a romper los ciclos. Aquí es donde dos funciones intentan usarse entre sí. Cuando esto sucede (y es una cosa perfectamente válida), puede #incluir un archivo de encabezado, pero ese archivo de encabezado intenta # incluir el archivo de encabezado que está escribiendo actualmente ... que luego # incluye el otro encabezado , que incluye el que estás escribiendo. Estás atrapado en una situación de huevo y gallina con cada archivo de encabezado intentando #incluir el otro. Para resolver esto, puede declarar hacia adelante las partes que necesita en uno de los archivos y dejar el #include fuera de ese archivo.
P.ej:
File Car.h
File Wheel.h
Hmm ... aquí se requiere la declaración de Car ya que Wheel tiene un puntero a un Car, pero Car.h no se puede incluir aquí ya que daría lugar a un error del compilador. Si se incluye Car.h, eso trataría de incluir Wheel.h, que incluiría Car.h, que incluiría Wheel.h, y esto continuaría para siempre, por lo que el compilador genera un error. La solución es reenviar declarar Car en su lugar:
Si la clase Wheel tuviera métodos que necesitaran llamar métodos de auto, esos métodos podrían definirse en Wheel.cpp y Wheel.cpp ahora puede incluir Car.h sin causar un ciclo.
fuente
// From Car.h
puede crear situaciones difíciles tratando de encontrar una definición en el futuro , garantizado.El compilador busca cada símbolo que se utiliza en la unidad de traducción actual que se haya declarado previamente o no en la unidad actual. Es solo una cuestión de estilo proporcionar todas las firmas de métodos al comienzo de un archivo fuente, mientras que las definiciones se proporcionan más adelante. Su uso significativo es cuando usa un puntero a una clase como variable miembro de otra clase.
Por lo tanto, use declaraciones directas en las clases siempre que sea posible. Si su programa solo tiene funciones (con archivos de encabezado ho), proporcionar prototipos al principio es solo una cuestión de estilo. De todos modos, este sería el caso si el archivo de encabezado estuviera presente en un programa normal con encabezado que solo tiene funciones.
fuente
Debido a que C ++ se analiza de arriba hacia abajo, el compilador necesita saber sobre las cosas antes de usarlas. Entonces, cuando hace referencia a:
en la función principal, el compilador necesita saber que existe. Para probar esto, intente moverlo debajo de la función principal y obtendrá un error de compilación.
Entonces, una ' Declaración de avance ' es justo lo que dice en la lata. Está declarando algo antes de su uso.
En general, incluiría declaraciones de reenvío en un archivo de encabezado y luego incluiría ese archivo de encabezado de la misma manera que se incluye iostream .
fuente
El término " declaración directa " en C ++ se usa principalmente para declaraciones de clase . Vea (al final de) esta respuesta para saber por qué una "declaración directa" de una clase es realmente una simple declaración de clase con un nombre elegante.
En otras palabras, el "reenvío" simplemente agrega lastre al término, ya que cualquier declaración puede verse como reenviada en la medida en que declara algún identificador antes de ser utilizado.
(En cuanto a qué es una declaración en lugar de una definición , nuevamente vea ¿Cuál es la diferencia entre una definición y una declaración? )
fuente
Cuando el compilador ve
add(3, 4)
que necesita saber qué significa eso. Con la declaración de reenvío, básicamente le dice al compilador queadd
es una función que toma dos entradas y devuelve un int. Esta es información importante para el compilador porque necesita poner 4 y 5 en la representación correcta en la pila y necesita saber de qué tipo es la cosa que devuelve add.En ese momento, el compilador no está preocupado por la actual implementación de
add
, es decir, donde está (o si no es incluso uno) y si se compila. Esto aparece más tarde, después de compilar los archivos de origen cuando se invoca el vinculador.fuente
Es lo mismo que
#include"add.h"
. Si lo sabe, el preprocesador expande el archivo que menciona#include
en el archivo .cpp donde escribe la#include
directiva. Eso significa que, si escribes#include"add.h"
, obtienes lo mismo, es como si estuvieras haciendo una "declaración directa".Supongo que
add.h
tiene esta línea:fuente
Un apéndice rápido con respecto a: por lo general, coloca esas referencias en un archivo de encabezado que pertenece al archivo .c (pp) donde se implementa la función / variable, etc. en su ejemplo se vería así: add.h:
la palabra clave extern establece que la función se declara realmente en un archivo externo (también podría ser una biblioteca, etc.). su main.c se vería así:
fuente
Un problema es que el compilador no sabe qué tipo de valor entrega su función; se supone que la función devuelve un
int
en este caso, pero esto puede ser tan correcto como incorrecto. Otro problema es que el compilador no sabe qué tipo de argumentos espera su función y no puede advertirle si está pasando valores del tipo incorrecto. Hay reglas especiales de "promoción", que se aplican al pasar, digamos valores de punto flotante a una función no declarada (el compilador tiene que ampliarlas para escribir double), que a menudo no es lo que la función realmente espera, lo que lleva a errores difíciles de encontrar en tiempo de ejecución.fuente