Что такое форвардные объявления на С++?

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

Указано следующее:

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

Мы использовали форвардное объявление, чтобы компилятор знал, что было "add" при компиляции main.cpp. Как упоминалось ранее, запись деклараций для каждой функции, которую вы хотите использовать, которая живет в другом файле, может оказаться утомительной.

Можете ли вы объяснить " форвардная декларация" дальше? В чем проблема, если мы используем его в функции main()?

Ответ 1

Почему forward-declare необходимо в С++

Компилятор хочет убедиться, что вы не совершили орфографических ошибок или не передали неправильное количество аргументов функции. Таким образом, он настаивает на том, что он сначала видит объявление "добавить" (или любые другие типы, классы или функции) до его использования.

Это действительно позволяет компилятору лучше выполнять проверку кода и позволяет ему убирать свободные концы, чтобы он мог создавать аккуратный выглядящий объектный файл. Если вам не нужно было пересылать объявления, компилятор создаст объектный файл, который должен содержать информацию обо всех возможных догадках относительно функции "добавить" . И компоновщик должен будет содержать очень умную логику, чтобы попытаться определить, какую "добавленную" вы на самом деле намеревались вызывать, когда функция "добавить" может жить в другом объектном файле, компоновщик соединяется с тем, который использует add для создания dll или exe. Возможно, что компоновщик может ошибиться в добавлении. Предположим, вы хотели использовать int add (int a, float b), но случайно забыли написать его, но компоновщик нашел уже существующий int add (int a, int b) и подумал, что это был правильный, и вместо этого использовал это. Ваш код будет компилироваться, но не будет делать то, что вы ожидали.

Итак, просто для того, чтобы держать вещи явными и избегать гадания и т.д., компилятор настаивает на том, что вы объявляете все до его использования.

Разница между объявлением и определением

В стороне, важно знать разницу между декларацией и определением. Декларация просто дает достаточно кода, чтобы показать, что-то похоже, поэтому для функции это тип возврата, вызов конвенции, имя метода, аргументы и их типы. Но код метода не требуется. Для определения вам потребуется объявление, а затем также код для функции.

Как форвардные объявления могут значительно сократить время сборки

Вы можете получить объявление функции в ваш текущий .cpp или .h файл с помощью # include'в заголовке, который уже содержит объявление функции. Однако это может замедлить компиляцию, особенно если вы # включите заголовок в .h вместо .cpp вашей программы, так как все, что # включает в себя .h, которое вы пишете, будет включать # include'все все заголовки вы написали #includes for too. Внезапно компилятор имеет # вложенные страницы и страницы кода, которые он должен компилировать, даже если вы только хотели использовать одну или две функции. Чтобы этого избежать, вы можете использовать форвардную декларацию и просто вводить декларацию функции самостоятельно в верхней части файла. Если вы используете только несколько функций, это может сделать ваши компиляции быстрее по сравнению с всегда # включая заголовок. Для действительно больших проектов разница может составлять час или больше времени компиляции, скупаемого до нескольких минут.

Разрыв циклических ссылок, где два определения используют друг друга

Кроме того, форвардные объявления могут помочь вам сломать циклы. Здесь две функции пытаются использовать друг друга. Когда это происходит (и это совершенно правильная вещь), вы можете # включить один заголовочный файл, но этот заголовочный файл пытается # включить заголовочный файл, который вы сейчас пишете.... который затем # включает другой заголовок, который # включает тот, который вы пишете. Вы застряли в ситуации с курятиной и яйцом, каждый заголовочный файл пытается повторно включить #include. Чтобы решить эту проблему, вы можете перенаправлять объявления в один из файлов и оставлять #include из этого файла.

Например:

Файл Car.h

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

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

Файл Wheel.h

Хм... здесь требуется объявление автомобиля, так как Wheel имеет указатель на автомобиль, но Car.h не может быть включен здесь, так как это приведет к ошибке компилятора. Если бы Car.h был включен, это попыталось бы включить Wheel.h, который будет включать Car.h, который будет включать Wheel.h, и это будет продолжаться вечно, поэтому вместо компилятора возникает ошибка. Решение состоит в том, чтобы переслать объявление Car вместо:

class Car;     // forward declaration

class Wheel
{
    Car* car;
};

Если у класса Wheel были методы, которые нужно вызвать методы автомобиля, эти методы могут быть определены в Wheel.cpp, а Wheel.cpp теперь может включать Car.h, не вызывая цикл.

Ответ 2

Компилятор ищет, что каждый символ, используемый в текущем блоке перевода, ранее объявлен или нет в текущем блоке. Это всего лишь вопрос стиля, обеспечивающий все сигнатуры методов в начале исходного файла, в то время как определения предоставляются позже. Значительное его использование заключается в использовании указателя на класс в качестве переменной-члена другого класса.

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

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

Итак, используйте forward-declarations в классах, когда это возможно. Если ваша программа имеет только функции (с файлами заголовков ho), то предоставление прототипов в начале - это всего лишь вопрос стиля. Это было бы так или иначе, если бы заголовочный файл присутствовал в обычной программе с заголовком, который имеет только функции.

Ответ 3

Поскольку С++ анализируется сверху вниз, компилятор должен знать о вещах до их использования. Итак, когда вы ссылаетесь:

int add( int x, int y )

в основной функции, которую компилятор должен знать, существует. Чтобы доказать это, переместите его ниже основной функции, и вы получите ошибку компилятора.

Итак, Forward Declaration - это то, что он говорит о жесте. Он объявляет что-то заранее о его использовании.

Как правило, вы включаете объявления вперед в файл заголовка, а затем включаете этот заголовочный файл так же, как и iostream.

Ответ 4

Термин " forward декларация" в С++ используется, главным образом, только для объявлений классов. См. (Конец) этот ответ, почему "прямое объявление" класса действительно просто декларация класса с причудливым именем.

Другими словами, "forward" просто добавляет балласт к термину, так как любое объявление можно рассматривать как переходящее, поскольку оно объявляет некоторый идентификатор до его использования.

(Что касается объявления в отличие от определения , снова см. В чем разница между определением и объявление?)

Ответ 5

Когда компилятор видит add(3, 4), он должен знать, что это значит. С помощью прямого объявления вы в основном говорите компилятору, что add - это функция, которая принимает два int и возвращает int. Это важная информация для компилятора, потому что она должна помещать 4 и 5 в правильное представление в стек и должна знать, какой тип возвращает вещь, добавленная добавлением.

В то время компилятор не беспокоится о фактической реализации add, то есть где он (или есть даже один), и если он компилируется. Это появляется позже, после компиляции исходных файлов при вызове компоновщика.

Ответ 6

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

Можете ли вы объяснить "форвардную декларацию" более подробно? В чем проблема, если мы используем его в функции main()?

То же, что и #include"add.h". Если вы знаете, препроцессор расширяет файл, указанный в #include, в .cpp файле, где вы пишете директиву #include. Это означает, что если вы пишете #include"add.h", вы получите то же самое, это значит, что вы делаете "декларирование вперед".

Я предполагаю, что add.h имеет следующую строку:

int add(int x, int y); 

Ответ 7

Одна из проблем заключается в том, что компилятор не знает, какое значение передается вашей функцией; предполагает, что функция возвращает int в этом случае, но это может быть так же правильно, как это может быть неправильно. Другая проблема заключается в том, что компилятор не знает, какие аргументы ожидает ваша функция и не может вас предупредить, если вы передаете значения неправильного типа. Существуют специальные правила "продвижения", которые применяются при передаче, говорят о значениях с плавающей запятой для необъявленной функции (компилятор должен расширять их, чтобы вводить double), чего часто нет, что функция действительно ожидает, что приводит к затруднению поиска ошибок во время выполнения.

Ответ 8

одно быстрое добавление: обычно вы помещаете эти прямые ссылки в файл заголовка, принадлежащий файлу .c(pp), где реализована функция/переменная и т.д. в вашем примере это будет выглядеть так: add.h:

extern int add(int a, int b);

ключевое слово extern утверждает, что функция фактически объявлена ​​во внешнем файле (также может быть библиотекой и т.д.). ваш main.c будет выглядеть следующим образом:

#include 
#include "add.h"

int main()
{
.
.
.