2011-01-21 18 views
130

: http://www.learncpp.com/cpp-tutorial/19-header-files/¿Qué son declaraciones directas en C++? En

A continuación se menciona:

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; 
} 

Utilizamos una declaración hacia adelante para que la el compilador sabría qué era "add" al compilar main.cpp. Como se mencionó anteriormente, escribir declaraciones avanzadas para cada función que quiera usar y que viva en otro archivo puede ser tedioso rápidamente.

¿Puedes explicarnos "forward declaration" más allá? ¿Cuál es el problema si lo usamos en la función main()?

+0

Una "declaración hacia adelante" en realidad es sólo una declaración. Vea (al final de) esta respuesta: http://stackoverflow.com/questions/1410563/what-is-the-difference-between-a-definition-and-a-declaration/1410632#1410632 – sbi

Respuesta

266

¿Por qué es necesaria en C++

El compilador quiere asegurarse de no haber cometido errores de ortografía o superado el número incorrecto de argumentos con visión de declarar 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 ser utilizada.

Esta realidad sólo permite que el compilador para hacer un mejor trabajo de validar el código, y permite que poner en orden los cabos sueltos por lo que puede producir un archivo de objeto que mira aseado. Si no tuviera que reenviar declarar cosas, el compilador produciría un archivo de objeto que debería contener información sobre todas las conjeturas posibles sobre cuál podría ser la función 'agregar'. Y el enlazador debería contener una lógica muy inteligente para tratar de determinar qué 'agregar' realmente desea llamar, cuando la función 'agregar' puede vivir en un archivo de objeto diferente al que el enlazador se une con el que usa agregar para producir un dll o exe Es posible que el enlazador reciba el complemento incorrecto. Digamos que quería usar int add (int a, float b), pero accidentalmente olvidó escribirlo, pero el enlazador encontró una int existente (int a, int b) y pensó que era la correcta y la usó en su lugar. Su código se compilaría, pero no haría lo que esperaba.

tanto, sólo para mantener las cosas explícitas y evitar el adivinanzas, etc., insiste en que el compilador se declara todo antes de que se utiliza.

Diferencia entre declaración y definición

Dicho sea de paso, es importante saber la diferencia entre una declaración y una definición. Una declaración simplemente proporciona suficiente código para mostrar cómo se ve algo, por lo que para una función, este es el tipo de retorno, la convención de llamadas, 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 luego también el código para la función.

Cómo adelante-declaraciones pueden reducir significativamente los tiempos de construcción

Puede obtener la declaración de una función en su .cpp actual o archivo .h por # includ'ing la cabecera que ya contiene una declaración de la función. Sin embargo, esto puede ralentizar su compilación, especialmente si #incluye un encabezado en .h en lugar de .cpp de su programa, ya que todo lo que #incluye la .h que está escribiendo terminaría #incluyendo todos los encabezados usted escribió #includes también. De repente, el compilador tiene # páginas incluidas y páginas de código que necesita compilar, incluso cuando solo quería usar una o dos funciones. Para evitar esto, puede usar una declaración directa y simplemente escribir la declaración de la función en la parte superior del archivo. Si solo está usando algunas funciones, esto realmente puede hacer que sus compilaciones sean más rápidas en comparación con siempre # incluyendo el encabezado. Para proyectos realmente grandes, la diferencia podría ser una hora o más de tiempo de compilación comprado hasta unos minutos.

rotura referencias cíclicas, donde dos definiciones tanto utilizan entre sí

Además, con visión de declaraciones pueden ayudar a romper los ciclos. Aquí es donde dos funciones ambas intentan usar el uno al otro. 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á escribiendo. Estás atrapado en una situación de huevo y pollo con cada archivo de cabecera tratando de incluir # el otro. Para resolver esto, puede reenviar-declarar las partes que necesita en uno de los archivos y dejar el #include fuera de ese archivo.

Ej:

Archivo Car.h

#include "Wheel.h" // Include Wheel's definition so it can be used in Car. 
#include <vector> 

class Car 
{ 
    std::vector<Wheel> wheels; 
}; 

Archivo Wheel.h

Hmm ... Es necesaria la declaración de coche aquí como la rueda tiene un puntero a un Auto, pero Car.h no se puede incluir aquí ya que resultaría en un error de compilación. Si se incluyera Car.h, eso intentaría 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 coche en su lugar:

class Car;  // forward declaration 

class Wheel 
{ 
    Car* car; 
}; 

Si la rueda de clase había métodos que deben llamar a los métodos de coche, esos métodos podrían definirse en Wheel.cpp y Wheel.cpp ahora es capaz de incluir Car.h sin causar un ciclo

+3

forward declaration también es necesario cuando una función es amigable para dos o muchas clases – Barun

+0

Hey Scott, en tu punto sobre tiempos de compilación: ¿Dirías que es una práctica común/mejor para siempre reenviar declarar e incluir encabezados según sea necesario en el archivo .cpp ? Al leer su respuesta, parece que debería ser así, pero me pregunto si hay alguna advertencia. – Zepee

+4

@Zepee Es un equilibrio. Para compilaciones rápidas, diría que es una buena práctica y recomiendo probarla. Sin embargo, puede requerir un poco de esfuerzo y líneas de código adicionales que pueden necesitar mantenerse y actualizarse si los nombres de tipo, etc. todavía se cambian (aunque las herramientas mejoran al renombrar cosas automáticamente). Entonces hay una compensación. He visto bases de código donde nadie molesta. Si se encuentra repitiendo las mismas definiciones hacia adelante, siempre puede ponerlas en un archivo de encabezado separado e incluir eso, algo así como: http://stackoverflow.com/questions/4300696/what-is-the-iosfwd-header –

10

Dado que C++ se analiza de arriba hacia abajo, el compilador necesita conocer las cosas antes de que se utilicen. Entonces, cuando usted hace referencia:

int add(int x, int y) 

en la función principal que el compilador necesita saber que existe. Para probar esto, intente moverlo debajo de la función principal y obtendrá un error de compilación.

Así que un 'Delantero Declaración' es justo lo que dice en la lata. Está declarando algo antes de su uso.

En general, debe incluir declaraciones directas en un archivo de encabezado y luego incluir ese archivo de encabezado de la misma manera que se incluye iostream.

0

Un problema es que el compilador no sabe qué tipo de valor entrega su función; se supone que la función devuelve int en este caso, pero esto puede ser tan correcto como erróneo. 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 cuando se pasan, dicen valores de coma flotante a una función no declarada (el compilador debe ampliarlos para que digan doble), 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.

1

Cuando el compilador ve add(3, 4), necesita saber lo que eso significa. Con la declaración directa básicamente le dice al compilador que add es una función que toma dos ints 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 qué tipo devuelve la cosa con add.

En ese momento, el compilador no está preocupado por la implementación real de add, es decir, donde está (o si no es incluso uno) y si se compila. Esto aparece más adelante, después de compilando los archivos fuente cuando se invoca el enlazador.

1
int add(int x, int y); // forward declaration using function prototype 

Puede explicar "declaración hacia adelante" más allá? ¿Cuál es el problema si lo usamos en la función main()?

Es igual que #include"add.h". Si sabe, el preprocesador expande el archivo que menciona en #include, en el archivo .cpp donde escribe la directiva #include. Eso significa que si escribe #include"add.h", obtendrá lo mismo, es como si estuviera haciendo una "declaración directa".

Estoy asumiendo que add.h tiene esta línea:

int add(int x, int y); 
0

una adición rápida en relación con: por lo general que poner esas referencias hacia delante en un archivo de cabecera que pertenece al .c (pp) presentar donde la función/variable de etc. está implementado. en su ejemplo se vería así: add.h:

extern int add(int a, int b); 

los estados de palabras clave extern que la función es en realidad declaró en un archivo externo (también podría ser una biblioteca, etc.). su main.c se vería así:

 
#include 
#include "add.h" 

int main() 
{ 
. 
. 
. 

+0

Pero, no lo haga solo ponemos las declaraciones en el archivo de encabezado? Creo que esta es la razón por la cual la función se define en "add.cpp" y, por lo tanto, usa declaraciones forward? Gracias. – Simplicity

23

El compilador busca cada símbolo que se está utilizando en la unidad de traducción actual previamente declarada o no en la unidad actual. Es solo una cuestión de estilo al 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.

//foo.h 
class bar; // This is useful 
class foo 
{ 
    bar* obj; // Pointer or even a reference. 
}; 

// foo.cpp 
#include "bar.h" 
#include "foo.h" 

Por lo tanto, utilice declaraciones forward en las clases siempre que sea posible. Si su programa solo tiene funciones (con archivos de encabezado ho), entonces 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.

7

El término "declaración adelantada" en C++ es en su mayoría sólo se utiliza para declaraciones de clase. Consulte (el final de) this answer 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 "adelante" sólo se suma lastre para el término, como ninguna declaración puede ser visto como hacia adelante en la medida en que declara algún identificador antes de su uso.

(En cuanto a lo que es una declaración en contraposición a una definición, volver a ver What is the difference between a definition and a declaration?)

Cuestiones relacionadas