Форвардная декларация
В компьютерном программировании предварительное объявление — это объявление идентификатора (обозначающего объект , такой как тип, переменная, константа или функция), для которого программист еще не дал полного определения .
необходимо Компилятору знать определенные свойства идентификатора (размер для выделения памяти , тип данных для проверки типа, например сигнатуру типа функций), но не другие детали, такие как конкретное значение, которое он содержит (в случае переменных или константы) или определение (в случае функций). Это особенно полезно для однопроходных компиляторов и отдельной компиляции .
Форвардное объявление используется в языках, которые требуют объявления перед использованием; это необходимо для взаимной рекурсии в таких языках, так как невозможно определить такие функции (или структуры данных) без прямой ссылки в одном определении: одна из функций (соответственно структур данных) должна быть определена первой. Также полезно обеспечить гибкую организацию кода, например, если вы хотите разместить основную часть вверху, а вызываемые функции — под ней.
В других языках предварительные объявления не требуются, вместо этого обычно требуется многопроходный компилятор и отсрочка некоторой компиляции до времени компоновки . В этих случаях идентификаторы должны быть определены (инициализированы переменные, определены функции), прежде чем их можно будет использовать во время выполнения, без необходимости предварительного определения в исходном коде для компиляции или интерпретации: идентификаторы не должны быть немедленно преобразованы в существующий объект. .
Примеры
[ редактировать ]Базовый пример на языке C:
void printThisInteger(int);
В C и C++ строка выше представляет собой предварительное объявление функции и является ее прототипом . После обработки этого объявления компилятор разрешит программному коду ссылаться на объект. printThisInteger
в остальной части программы. Определение функции должно быть где-то предоставлено (тот же файл или другой, где компоновщик будет обязан правильно сопоставить ссылки на конкретную функцию в одном или нескольких объектных файлах с определением, которое должно быть уникальным, в другом). :
void printThisInteger(int x) {
printf("%d\n", x);
}
Переменные могут иметь только предварительное объявление и не иметь определения. Во время компиляции они инициализируются правилами, специфичными для языка (неопределенными значениями, 0, указателями NULL, ...). Переменные, определенные в других исходных/объектных файлах, должны иметь предварительное объявление, указанное с помощью ключевого слова extern
:
int foo; //foo might be defined somewhere in this file
extern int bar; //bar must be defined in some other file
В Паскале и других языках программирования Вирта общим правилом является то, что все сущности должны быть объявлены перед использованием, и, таким образом, прямое объявление необходимо, например, для взаимной рекурсии. В C применяется то же общее правило, но за исключением необъявленных функций и неполных типов. Таким образом, в C можно (хотя и неразумно) реализовать пару взаимно рекурсивных функций следующим образом:
int first(int x) {
if (x == 0)
return 1;
else
return second(x-1); // forward reference to second
}
int second(int x) {
if (x == 0)
return 0;
else
return first(x-1); // backward reference to first
}
В Паскале та же реализация требует предварительного объявления second
предшествовать его использованию в first
. Без предварительного объявления компилятор выдаст сообщение об ошибке, указывающее, что идентификатор second
был использован без объявления.
Классы
[ редактировать ]В некоторых объектно-ориентированных языках, таких как C++ и Objective-C , иногда необходимо предварительно объявлять классы. Это делается в ситуациях, когда необходимо знать, что имя класса является типом, но когда знать структуру необязательно.
В C++ классы и структуры могут быть объявлены вперед следующим образом:
class MyClass;
struct MyStruct;
В C++ классы могут быть объявлены вперед, если вам нужно использовать только тип указателя на этот класс (поскольку все указатели на объекты имеют одинаковый размер, и это то, о чем заботится компилятор). Это особенно полезно внутри определений классов, например, если класс содержит член, который является указателем (или ссылкой) на другой класс.
Упреждающее объявление используется, чтобы избежать ненужной связи, что помогает сократить время компиляции за счет уменьшения количества включений заголовков. Это имеет тройное преимущество:
- уменьшить количество файлов, открываемых #include (следовательно, количество вызовов операционной системы)
- уменьшение объема предварительно обработанных файлов (поскольку заголовок не включен)
- уменьшение влияния перекомпиляции при изменении заранее объявленного класса.
Предварительного объявления класса недостаточно, если вам нужно использовать фактический тип класса, например, если у вас есть член, тип которого является непосредственно этим классом (а не указатель), или если вам нужно использовать его в качестве базового класса, или если вам нужно использовать методы класса в методе.
В Objective-C классы и протоколы могут быть объявлены заранее следующим образом:
@class MyClass;
@protocol MyProtocol;
В Objective-C классы и протоколы могут быть объявлены вперед, если вам нужно использовать их только как часть типа указателя объекта, например МойКласс * или id<МойПротокол> . Это особенно полезно внутри определений классов, например, если класс содержит член, который является указателем на другой класс; Чтобы избежать циклических ссылок (т. е. этот класс может также содержать член, который является указателем на этот класс), вместо этого мы просто объявляем классы вперед.
Предварительного объявления класса или протокола недостаточно, если вам нужно создать подкласс этого класса или реализовать этот протокол.
Прямая ссылка
[ редактировать ]Термин «передовая ссылка» иногда используется как синоним « форвардного объявления» . [1] Однако чаще всего перед каким-либо объявлением подразумевается фактическое использование объекта; то есть первое упоминание о second
в приведенном выше коде является прямой ссылкой. [2] [3] Таким образом, мы можем сказать, что, поскольку в Паскале форвардные объявления обязательны, прямые ссылки запрещены.
Пример (действительной) прямой ссылки в C++ :
class C {
public:
void mutator(int x) { myValue = x; }
int accessor() const { return myValue; }
private:
int myValue;
};
В этом примере есть две ссылки на myValue
до того, как оно будет объявлено. C++ обычно запрещает прямые ссылки, но они разрешены в особых случаях, когда речь идет о членах класса. Поскольку функция-член accessor
не может быть скомпилирован, пока компилятор не узнает тип переменной-члена myValue
, компилятор обязан запомнить определение accessor
пока он не увидит myValue
декларация.
Разрешение прямых ссылок может значительно увеличить сложность и требования к памяти компилятора и, как правило, не позволяет реализовать компилятор за один проход .