¿Qué son las declaraciones directas en C++?
En este enlace se mencionó lo siguiente:
agregar.cpp:
int add(int x, int y)
{
return x + y;
}
principal.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 "
add
" qué era al compilarmain.cpp
. Como se mencionó anteriormente, escribir declaraciones directas para cada función que desee utilizar y que se encuentre en otro archivo puede resultar tedioso rápidamente.
¿ Puede explicar más la " declaración anticipada "? ¿Cuál es el problema si lo usamos en la main
función?
Por qué es necesaria la declaración directa en C++
El compilador quiere asegurarse de que no haya cometido errores ortográficos ni haya pasado una cantidad incorrecta de argumentos a la función. Por lo tanto, insiste en ver primero una declaración de 'agregar' (o cualquier otro tipo, clase o función) antes de usarlo.
Esto realmente sólo permite al compilador hacer un mejor trabajo al validar el código y le permite ordenar los cabos sueltos para que pueda producir un archivo objeto de apariencia ordenada. Si no tuviera que declarar cosas, el compilador produciría un archivo objeto que tendría que contener información sobre todas las posibles conjeturas sobre cuál add
podría ser la función. Y el vinculador tendría que contener una lógica muy inteligente para intentar determinar cuál add
realmente pretendía llamar, cuando la add
función puede residir en un archivo objeto diferente al que el vinculador se une con el que usa add para producir un dll
o exe
. Es posible que el vinculador se equivoque add
. Digamos que querías usarlo int add(int a, float b)
, pero accidentalmente olvidaste escribirlo, pero el vinculador encontró uno que ya existía int add(int a, int b)
y pensó que era el correcto y lo usó en su lugar. Su código se compilaría, pero no haría lo que esperaba.
Entonces, solo para mantener las cosas explícitas y evitar conjeturas, etc., el compilador insiste en que se declare todo antes de usarlo.
Diferencia entre declaración y definición
Además, 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, por lo 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. Sin embargo, no se requiere el código del método. Para una definición, necesita la declaración y luego también el código de la función.
Cómo las declaraciones anticipadas pueden reducir significativamente los tiempos de construcción
Puede obtener la declaración de una función en su archivo actual .cpp
#incluyendo .h
el encabezado que ya contiene una declaración de la función. Sin embargo, esto puede ralentizar tu compilación, especialmente si colocas #include
un encabezado en a .h
en lugar de .cpp
en tu programa, ya que todo lo que #incluye lo que .h
estás escribiendo terminaría #incluyendo todos los encabezados para los que escribiste #includes también. De repente, el compilador ha incluido páginas y páginas de código que necesita compilar incluso cuando solo desea utilizar una o dos funciones. Para evitar esto, puede utilizar una declaración directa y simplemente escribir la declaración de la función usted mismo en la parte superior del archivo. Si solo estás usando algunas funciones, esto realmente puede hacer que tus compilaciones sean más rápidas en comparación con incluir siempre el encabezado. Para proyectos realmente grandes, la diferencia podría ser una hora o más de tiempo de compilación reducido a unos pocos minutos.
Romper referencias cíclicas donde dos definiciones se usan entre sí
Además, las declaraciones anticipadas pueden ayudarle a romper ciclos. Aquí es donde dos funciones intentan usarse entre sí. Cuando esto sucede (y es algo perfectamente válido), puede utilizar #include
un archivo de encabezado, pero ese archivo de encabezado intenta acceder #include
al archivo de encabezado que está escribiendo actualmente... que luego #incluye el otro encabezado, que #incluye el uno que estás escribiendo. Estás atrapado en una situación como la de la gallina y el huevo, con cada archivo de encabezado intentando volver a #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:
Archivo coche.h
#include "Wheel.h" // Include Wheel's definition so it can be used in Car.
#include <vector>
class Car
{
std::vector<Wheel> wheels;
};
Rueda de archivos.h
Car
Hmm... aquí se requiere la declaración de ya que Wheel
tiene un puntero a a Car
, pero Car.h
no se puede incluir aquí ya que resultaría en un error del compilador. Si Car.h
estuviera incluido, entonces intentaría incluir Wheel.h
cuál incluiría Car.h
cuál 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:
class Car; // forward declaration
class Wheel
{
Car* car;
};
Si la clase Wheel
tuviera métodos que necesitaran llamar a métodos de Car
, esos métodos podrían definirse Wheel.cpp
y Wheel.cpp
ahora pueden incluirse Car.h
sin causar un ciclo.
El compilador busca cada símbolo que se utiliza en la unidad de traducción actual, si está declarado previamente o no en la unidad actual. Es sólo una cuestión de estilo proporcionar todas las firmas de los métodos al principio de un archivo fuente, mientras que las definiciones se proporcionan más adelante. Su uso importante es cuando se utiliza 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 directas 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. Este sería el caso de todos modos si el archivo de encabezado estuviera presente en un programa normal con un encabezado que solo tiene funciones.
Debido a que C++ se analiza de arriba hacia abajo, el compilador necesita conocer las cosas antes de usarlas. Entonces, cuando haces referencia:
int add( int x, int y )
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 del compilador.
Así que una ' Declaración Avanzada ' es justo lo que dice en la lata. Es declarar algo antes de su uso.
Generalmente incluiría declaraciones directas en un archivo de encabezado y luego incluiría ese archivo de encabezado de la misma manera que se incluye iostream .
El término " declaración directa " en C++ generalmente solo se usa para declaraciones de clases . Vea (el final de) esta respuesta para saber por qué una "declaración directa" de una clase en realidad es solo una simple declaración de clase con un nombre elegante.
En otras palabras, "forward" simplemente añade lastre al término, ya que cualquier declaración puede considerarse como forward en la medida en que declara algún identificador antes de ser utilizado.
(En cuanto a qué es una declaración en contraposición a una definición , consulte nuevamente ¿Cuál es la diferencia entre una definición y una declaración? )