Declaración de reenvío en C++

Dhruvdeep Singh Saini 12 octubre 2023
  1. Declaración de reenvío en C++
  2. Declaración directa de funciones en C++
  3. Declaración directa de clases en C++
  4. Por qué el compilador de C++ necesita una declaración de reenvío
  5. Ventajas de usar la declaración directa en C++
Declaración de reenvío en C++

Este artículo explicará las declaraciones directas y mostrará por qué son necesarias para el compilador en C++, junto con ejemplos de código.

Esto también discutirá las ventajas de usar declaraciones directas, resaltará la diferencia entre una declaración y una definición, y mostrará cómo usar la declaración directa para evitar el error de dependencia cíclica de los archivos C++.

Declaración de reenvío en C++

Una declaración directa es la declaración de la sintaxis de una función, es decir, su nombre, tipo de devolución, argumentos y el tipo de datos de los argumentos antes de usarla en su programa.

Antes de definir funciones, incluimos declaraciones directas para que el compilador sepa que la función está definida en alguna parte del programa. La declaración directa de funciones utilizadas en un archivo separado se forma usando el #include para tener el archivo.

Declaración directa de funciones en C++

Veamos cómo funciona la declaración directa dentro de un fragmento de código.

#include <iostream>
using namespace std;

// forward declaration of sub2
int sub2(int A, int B);

int main() {
  cout << "Difference: " << sub2(25, 10);
  return 0;
}

int sub2(int A, int B)  // Defining sub2 here
{
  return A - B;
}

Producción :

Difference: 15

Aquí tenemos una función llamada sub2 que devuelve la diferencia de dos parámetros int pasados. Declaramos sub2 antes de la parte principal y luego definimos nuestra función más adelante en el programa.

Antes de entrar en la explicación, es esencial conocer la distinción entre definición y declaración en C++.

  1. Declaración: una declaración proporciona información simple como el nombre de la función, sus argumentos y sus tipos de datos, su tipo de retorno, es decir, el prototipo de la función.
  2. Definición: una definición proporciona los detalles de la declaración de la función e incluye el fragmento de código que realizaría la función de la tarea.

Ahora, volvamos a la declaración directa. ¿Por qué se necesitaba la declaración directa de sub2 en el programa anterior?

Expliquemos con el mismo código, esta vez, sin usar declaración directa.

#include <iostream>
using namespace std;

int main() {
  cout << "Difference: " << sub2(25, 10);
  return 0;
}

int sub2(int A, int B)  // Defining sub2 here
{
  return A - B;
}

Producción :

 error: 'sub2' was not declared in this scope
    6 |     cout << "Difference: " << sub2(25, 10);
      |                               ^~~~

El programa anterior no tiene ningún problema, pero todavía muestra un error de que la función sub2 no fue declarada. Esto se debe a que se llama sub2 en la línea 6 pero no se define hasta más adelante en la línea 10.

Dado que C++ es un lenguaje analizado de arriba hacia abajo, construye un árbol de análisis desde la parte superior y necesita conocer de antemano las funciones antes de usarlas. No necesita definir la función antes de llamarla, pero debe declararla.

También puede definir una función, aquí sub2, antes de la función principal para evitar este tipo de errores. Sin embargo, en un programa con múltiples funciones que se llaman entre sí o archivos incluidos externamente, el error persistiría, razón por la cual usamos declaraciones de avance.

Declaración directa de clases en C++

También necesita una declaración directa de clases en C++. Mostremos cómo.

#include <iostream>
using namespace std;

// Forward declaration of classes One and Two
class One;
class Two;

class One {
  int y;

 public:
  void num(int a)  // Getting input number
  {
    y = a;
  }
  friend int sub2(One, Two);
};
class Two {
  int x;

 public:
  void num(int a)  // Getting input number
  {
    x = a;
  }
  friend int sub2(One, Two);
};
int sub2(One a, Two b)  // Subtraction of two numbers from both classes
{
  int ans = a.y - b.x;
  return ans;
}

int main() {
  Two y;
  One x;

  x.num(25);
  y.num(10);

  cout << "Difference: " << sub2(x, y);
  return 0;
}

Producción :

Difference: 15

El fragmento de código anterior contiene clases, Uno y Dos, ambas con una función num para obtener un valor y una función sub2 para restar dos números.

La declaración directa de ambas clases es necesaria para el programa anterior ya que la clase One incluye la función amiga sub2 con la clase Two mencionada en su parámetro.

Si eliminamos la declaración directa de clases en el fragmento de código anterior, obtenemos un mensaje de error:

15 |    [Error] 'Two' has not been declared In function 'int sub2(One, Two)':

El error muestra que el compilador necesita una declaración directa de funciones y clases antes de usarlas en un programa.

Por qué el compilador de C++ necesita una declaración de reenvío

La declaración directa es necesaria ya que ayuda al compilador a garantizar 3 cosas:

  • El programa es correcto y no tiene faltas de ortografía simbólicas.
  • Los argumentos de la función declarada son correctos.
  • La función declarada existe en el programa y se define a continuación.

Si no reenvió la declaración de las funciones, el compilador crearía un archivo de objeto adicional que contiene información con varias conjeturas sobre cuál podría ser su función.

Y el vinculador (un programa que vincula varios objetos y clases en un solo archivo de objeto ejecutable) tendría un problema de vinculación, ya que podría tener una función existente con el mismo nombre pero con argumentos de un tipo de datos diferente.

Por ejemplo, suponga que tiene una función int sub2(int a, int b). Sin la declaración directa, el enlazador podría confundirse con otra función existente int sub2(float a, float b).

Un compilador valida el código para un archivo limpio con una declaración de avance de C++. Sería mejor recordar que C++ podría compilar y ejecutar dicho programa en algunos casos.

Sin embargo, no proporcionaría el resultado esperado. Esta es la razón por la cual el compilador requiere declaraciones de avance antes de implementarlas o usarlas en su código.

Ventajas de usar la declaración directa en C++

Las declaraciones directas ayudan al compilador a validar mejor su código y ayudan a evitar problemas de vinculación. Pero también ayuda:

  1. Evite la contaminación del espacio de nombres: las declaraciones de reenvío ayudan a garantizar que no se extravíe ningún fragmento de código y evitan la contaminación de un espacio de nombres.

  2. Mejore el tiempo de compilación: puede agregar la declaración de una función en su programa C++ al incluir un archivo de encabezado, y el compilador analizará todos los tokens proporcionados en el archivo, lo que puede llevar mucho tiempo. Sin embargo, puede evitar este procesamiento prolongado y usar la declaración directa para las clases particulares que pretende usar en lugar del archivo cpp completo.

    Es posible que esto no afecte a los códigos más pequeños, pero es útil en proyectos más importantes, ya que puede minimizar los tiempos de compilación, lo que reduce la complejidad del tiempo. Entonces, en lugar de incluir un archivo completo de C++, puede usar una clase específica con la extensión .h.

  3. Evite la colisión de nombres: las declaraciones directas también ayudan a garantizar que no haya colisiones de tokens o nombres de preprocesadores en el programa si hay diferentes proyectos con nombres de clases o funciones coincidentes.

  4. Romper la dependencia cíclica: la declaración directa de una clase puede resolver las referencias cíclicas al declarar las partes particulares necesarias en un archivo y dejar el encabezado fuera de él.

Evite la dependencia circular mediante la declaración directa en C++

Dos clases relacionadas o usando las funciones de cada uno crean una relación circular. Esto se conoce como dependencia cíclica o circular.

Suponga que hay dos clases dentro de su programa donde ambas necesitan usar la otra. En ese caso, agregará un archivo de encabezado para uno, pero además intentará incluir el archivo de encabezado para la otra clase circularmente dependiente, creando un bucle en el que cada encabezado intenta tener el otro.

Veamos cómo evitar la dependencia cíclica:

#include <iostream>
#include <vector>

#include "Two.h"  // Defining Two as it is used in One

class One {
  std::vector<Two> two;
};

int main() { return 0; }

Nuestro programa ha incluido otro archivo llamado Two.h usando #include como se usa en la clase One. Incluir un archivo class.h y no un programa completo reduce significativamente los tiempos de compilación, como se explicó anteriormente.

Ahora, mira el contenido de Two.h.

#include "One.h"  // Defining One as it is used in Two

class Two {
  One* a;  // Two uses a pointer of One
};

Two.h contiene la clase Two que usa un puntero de clase One. Pero dado que ambos archivos contienen encabezados para incluir el otro archivo, quedará atrapado en una dependencia circular donde ambos archivos se siguen llamando entre sí. Esto se puede evitar utilizando una declaración directa en lugar de agregar un encabezado en Two.h de la siguiente manera:

#include <iostream>

class One;

class Two {
  One* a;  // Two uses a pointer of One
};

Sería mejor recordar que la declaración directa de una función requiere el conocimiento del nombre de la función y los argumentos que se usarán al definirla y requiere la duplicación de los valores predeterminados para los parámetros predeterminados.

Por lo tanto, debe prestar atención cuando utilice la declaración directa en sus programas.