O que são declarações de encaminhamento em C ++?

215

Em: http://www.learncpp.com/cpp-tutorial/19-header-files/

O seguinte é mencionado:

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 uma declaração direta para que o compilador soubesse o que " add" era ao compilar main.cpp. Como mencionado anteriormente, escrever declarações de encaminhamento para todas as funções que você deseja usar que residem em outro arquivo pode ser entediante rapidamente.

Você pode explicar mais a " declaração direta "? Qual é o problema se o usarmos na main()função?

Simplicidade
fonte
1
Uma "declaração direta" é realmente apenas uma declaração. Veja (no final de) esta resposta: stackoverflow.com/questions/1410563/…
sbi

Respostas:

381

Por que a declaração direta é necessária em C ++

O compilador deseja garantir que você não tenha cometido erros de ortografia ou passado o número errado de argumentos para a função. Portanto, ele insiste em ver primeiro uma declaração de 'add' (ou quaisquer outros tipos, classes ou funções) antes de ser usada.

Isso realmente permite que o compilador faça um trabalho melhor de validação do código e permite que ele solte pontas soltas para que possa produzir um arquivo de objeto com aparência elegante. Se você não tivesse que encaminhar declarar as coisas, o compilador produziria um arquivo de objeto que teria que conter informações sobre todas as suposições possíveis sobre qual poderia ser a função 'adicionar'. E o vinculador precisaria conter uma lógica muito inteligente para tentar descobrir qual 'add' você realmente pretendia chamar, quando a função 'add' pode viver em um arquivo de objeto diferente que o vinculador está se unindo ao que usa add para produzir uma DLL ou EXE. É possível que o vinculador receba a adição errada. Digamos que você queira usar int add (int a, float b), mas acidentalmente esqueceu de escrevê-lo, mas o vinculador encontrou um int add já existente (int a, int b) e achou que era o correto e usou isso em seu lugar. Seu código seria compilado, mas não faria o que você esperava.

Portanto, apenas para manter as coisas explícitas e evitar adivinhações, etc, o compilador insiste em que você declare tudo antes de usá-lo.

Diferença entre declaração e definição

Como um aparte, é importante saber a diferença entre uma declaração e uma definição. Uma declaração apenas fornece código suficiente para mostrar como algo se parece; portanto, para uma função, esse é o tipo de retorno, convenção de chamada, nome do método, argumentos e seus tipos. Mas o código para o método não é necessário. Para uma definição, você precisa da declaração e também do código para a função.

Como declarações futuras podem reduzir significativamente os tempos de construção

Você pode obter a declaração de uma função no seu arquivo .cpp ou .h atual # incluindo o cabeçalho que já contém uma declaração da função. No entanto, isso pode atrasar sua compilação, especialmente se você # incluir um cabeçalho em .h em vez de .cpp do seu programa, pois tudo o que #inclui o .h que você está escrevendo acabará # incluindo todos os cabeçalhos você escreveu #includes for also. De repente, o compilador inclui # páginas e páginas de código que ele precisa compilar, mesmo quando você só queria usar uma ou duas funções. Para evitar isso, você pode usar uma declaração direta e apenas digitar a declaração da função na parte superior do arquivo. Se você estiver usando apenas algumas funções, isso pode realmente tornar suas compilações mais rápidas em comparação a sempre #incluindo o cabeçalho. Para projetos realmente grandes,

Quebrar referências cíclicas em que duas definições se usam

Além disso, as declarações avançadas podem ajudá-lo a interromper os ciclos. É aqui que duas funções tentam se usar. Quando isso acontece (e é uma coisa perfeitamente válida a ser feita), você pode # incluir um arquivo de cabeçalho, mas esse arquivo de cabeçalho tenta # incluir o arquivo de cabeçalho que você está escrevendo atualmente .... que, em seguida, inclui o outro cabeçalho , que # inclui o que você está escrevendo. Você está preso em uma situação de galinha e ovo, com cada arquivo de cabeçalho tentando # incluir o outro. Para resolver isso, você pode encaminhar novamente as partes necessárias em um dos arquivos e deixar o #include fora desse arquivo.

Por exemplo:

Arquivo Car.h

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

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

Arquivo Wheel.h

Hmm ... a declaração de Car é necessária aqui, pois Wheel tem um ponteiro para um Car, mas Car.h não pode ser incluído aqui, pois resultaria em um erro do compilador. Se Car.h fosse incluído, isso tentaria incluir Wheel.h, que incluiria Car.h, que incluiria Wheel.he isso continuaria para sempre, portanto, o compilador gera um erro. A solução é encaminhar declarar Car em vez disso:

class Car;     // forward declaration

class Wheel
{
    Car* car;
};

Se a classe Wheel tiver métodos que precisam chamar métodos de carro, esses métodos poderão ser definidos em Wheel.cpp e Wheel.cpp agora poderá incluir Car.h sem causar um ciclo.

Scott Langham
fonte
4
declaração para a frente também é necessário quando uma função é amigável para duas ou muitas classes
Barun
1
Ei Scott, sobre o seu ponto de vista sobre os tempos de compilação: você diria que é uma prática comum / sempre sempre encaminhar declarar e incluir cabeçalhos conforme necessário no arquivo .cpp? Ao ler sua resposta, parece que deveria ser assim, mas estou me perguntando se há alguma ressalva.
Zepee
5
@ Zepee É um equilíbrio. Para compilações rápidas, eu diria que é uma boa prática e recomendo tentar. No entanto, pode ser necessário algum esforço e linhas de código extras que talvez precisem ser mantidas e atualizadas se nomes de tipo etc. ainda estiverem sendo alterados (embora as ferramentas estejam melhorando em renomear coisas automaticamente). Portanto, há uma troca. Eu vi bases de código onde ninguém se incomoda. Se você se encontra repetindo as mesmas definições de encaminhamento, sempre pode colocá-las em um arquivo de cabeçalho separado e incluir isso, algo como: stackoverflow.com/questions/4300696/what-is-the-iosfwd-header
Scott Langham
declarações de encaminhamento são necessárias quando os arquivos de cabeçalho se referem um ao outro: ie stackoverflow.com/questions/396084/…
Nicholas Hamilton
1
Eu posso ver isso permitindo que os outros desenvolvedores da minha equipe sejam realmente maus cidadãos da base de código. Se você não precisar de um comentário com a declaração a seguir, // From Car.hpoderá criar algumas situações difíceis tentando encontrar uma definição no caminho, garantida.
Dagrooms
25

O compilador procura por cada símbolo que está sendo usado na unidade de conversão atual anteriormente declarado ou não na unidade atual. É apenas uma questão de estilo, fornecendo todas as assinaturas de métodos no início de um arquivo de origem, enquanto as definições são fornecidas posteriormente. O uso significativo disso é quando você usa um ponteiro para uma classe como variável de membro de outra classe.

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

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

Portanto, use declarações avançadas nas classes sempre que possível. Se o seu programa tiver apenas funções (com arquivos de cabeçalho ho), fornecer protótipos no início é apenas uma questão de estilo. De qualquer forma, isso ocorreria se o arquivo de cabeçalho estivesse presente em um programa normal com cabeçalho que possui apenas funções.

Mahesh
fonte
12

Como o C ++ é analisado de cima para baixo, o compilador precisa saber sobre as coisas antes de serem usadas. Então, quando você faz referência:

int add( int x, int y )

na função principal, o compilador precisa saber que existe. Para provar isso, tente movê-lo para abaixo da função principal e você receberá um erro do compilador.

Portanto, uma ' Declaração Avançada ' é exatamente o que diz na lata. Está declarando algo antes de seu uso.

Geralmente você incluiria declarações de encaminhamento em um arquivo de cabeçalho e, em seguida, o incluiria da mesma maneira que o iostream é incluído.

usuario
fonte
12

O termo " declaração direta " em C ++ é usado principalmente apenas para declarações de classe . Veja (no final) esta resposta para saber por que uma "declaração direta" de uma classe realmente é apenas uma declaração simples de classe com um nome sofisticado.

Em outras palavras, o "encaminhamento" apenas adiciona lastro ao termo, pois qualquer declaração pode ser vista como encaminhada na medida em que declara algum identificador antes de ser usado.

( Sobre o que é uma declaração e não uma definição , consulte novamente Qual é a diferença entre uma definição e uma declaração? )

sbi
fonte
2

Quando o compilador vê add(3, 4), precisa saber o que isso significa. Com a declaração forward, você basicamente diz ao compilador que addé uma função que recebe duas entradas e retorna um int. Essas são informações importantes para o compilador, pois ele precisa colocar 4 e 5 na representação correta na pilha e precisa saber que tipo de coisa retornada pelo add é.

Naquela época, o compilador não está preocupado com a real implementação de add, ou seja, onde é (ou se não é mesmo um) e se ele compila. Isso aparece depois, depois de compilar os arquivos de origem quando o vinculador é chamado.

René Nyffenegger
fonte
1
int add(int x, int y); // forward declaration using function prototype

Você pode explicar mais a "declaração direta"? Qual é o problema se o usarmos na função main ()?

É o mesmo que #include"add.h". Se você souber, o pré-processador expande o arquivo mencionado #include, no arquivo .cpp em que você escreve a #includediretiva. Isso significa que, se você escreve #include"add.h", recebe a mesma coisa, é como se estivesse fazendo uma "declaração de encaminhamento".

Estou assumindo que add.htem esta linha:

int add(int x, int y); 
Nawaz
fonte
1

um adendo rápido sobre: ​​geralmente você coloca essas referências em um arquivo de cabeçalho pertencente ao arquivo .c (pp) em que a função / variável etc. é implementada. no seu exemplo, ficaria assim: add.h:

extern int add (int a, int b);

a palavra-chave extern afirma que a função é realmente declarada em um arquivo externo (também pode ser uma biblioteca etc.). seu main.c ficaria assim:

#incluir 
#include "add.h"

int main ()
{
.
.
.

jack
fonte
Mas, não colocamos apenas as declarações no arquivo de cabeçalho? Eu acho que é por isso que a função é definida em "add.cpp" e, portanto, usando declarações avançadas? Obrigado.
Simplicity
0

Um problema é que o compilador não sabe que tipo de valor é entregue por sua função; é assumido, que a função retorne um intnesse caso, mas isso pode ser tão correto quanto errado. Outro problema é que o compilador não sabe que tipo de argumento sua função espera e não pode avisá-lo se você estiver passando valores do tipo errado. Existem regras especiais de "promoção", que se aplicam ao passar, digamos valores de ponto flutuante para uma função não declarada (o compilador precisa aumentá-los para digitar double), o que geralmente não é o que a função realmente espera, levando a erros difíceis de encontrar em tempo de execução.

Dirk
fonte