В теории программирования термин forward declaration означает декларирование идентификатора (обозначающего такой объект, как тип, переменная или функция) до того, как программист дал ему полное определение. Это требуется для того, чтобы компилятор знал тип идентификатора (чтобы можно было определить необходимый размер памяти для создания объекта, для проверки типа или подписи функции), при этом необязательно идентификатор может содержать значение (в случае переменной). Это также полезно для однопроходных компиляторов. Forward declaration используется в языках, которые требуют декларирования объекта перед его использованием (языки C, C++); это необходимо в таких языках для взаимной рекурсии (mutual recursion), поскольку невозможно определить эти функции без ссылки вперед в одном определении. Это также полезно для того, чтобы наглядно организовать структуру кода, например если Вы хотите разместить основную, главную часть кода в верхней части модуля, и ниже все вызываемые из этой главной части функции.
В других языках, где предварительное декларирование не нужно, обычно требуется вместо этого наличие многопроходного компилятора – идентификаторы должны быть определены (переменные инициализированы, тела функций определены) до того, как они будут использованы в процессе выполнения кода, но их не нужно определять до того, как они будут использованы в исходном коде для компиляции и интерпретации.
Давайте рассмотрим базовый пример предварительного декларирования функции на языке C:
void printThisInteger(int);
На языках C/C++ это декларирование представляет forward declaration функции, и это так называемый прототип функции. После обработки этой декларации компилятор позволяет программисту ссылаться на объект printThisInteger в остальной части программы (которая идет в тексте модуля за forward declaration функции printThisInteger, или вообще в другом модуле). Чтобы программа была работоспособна, определение для функции все равно должно быть где-то предоставлено (в том же самом файле модуля, или в другом - не важно; это уже обязанность линкера правильно задать соответствие ссылок для отдельной функции в одном или нескольких объектных файлах на определение этой функции):
void printThisInteger(int x)
{
printf("%d\n", x);
}
Переменные могут иметь только предварительное декларирование, без наличия определения. Во время компиляции кода они будут инициализированы по специальным правилам языка (в неопределенные значения, 0, указатели NULL, ...). Переменные, которые определены в другом исходном модуле (и соответственно появятся после компиляции в другом объектном файле), должны иметь forward declaration, представленную с ключевым словом extern (для функций это ключевое слово использовать необязательно):
int foo; //переменная foo должна быть определена где-то именно в этом файле
extern int bar; //переменная bar должна быть определена в каком-то другом файле
В Pascal и других Виртовских языках программирования, главное правило гласит: все сущности должны быть декларированы перед использованием, так что нужно использовать forward declaration, что необходимо, например, для взаимной рекурсии (mutual recursion). На языке C это главное правило тоже работает, но здесь есть исключение для неопределенных функций и незавершенных типов. Таким образом, на C можно (хотя и нежелательно) реализовать пару взаимно рекурсивных функций:
int first(int x)
{
if (x == 0)
return 1;
else
return second(x-1); // ссылка вперед на вторую функцию
}
int second(int x)
{
if (x == 0)
return 0;
else
return first(x-1); // ссылка назад на первую функцию
}
В Pascal та же самая реализация требует forward declaration для второй функции, чтобы она могла использоваться в первой. Без наличия такой forward declaration компилятор выдаст ошибку, сообщающую о том, что второй идентификатор используется до того, как он был определен.
[Классы]
На некоторых объектно-дезориентированных языках наподобие C++ и Objective-C, иногда требуется применить предварительное декларирование для классов (forward declare class). Это происходит в ситуациях, когда нужно знать, что имя класса - тип (но для структуры это знать не нужно).
На языке C++ классы и структуры могут быть forward-declared примерно так:
class MyClass;
struct MyStruct;
На языке C++ классы могут быть forward-declared, если только Вам нужно использовать тип указателя на этот класс (поскольку все указатели на объекты имеют одинаковый размер, и об этом должен позаботиться компилятор). Особенно это полезно внутри определений класса, например если класс содержит в себе член, который является указываетeлем на другой класс; чтобы избежать цикличных ссылок (например, этот класс может также содержать член, который указывает на этот же класс), мы просто вместо этого делаем предварительное декларирование классов.
Forward declaration для класса недостаточно, если Вам нужно использовать действительный тип класса. Например, если Вы имеете член класса, который имеет тип не указателя, а напрямую задает класс, или если Вы используете его как базовый класс, или если Вам нужно использовать методы этого класса в другом методе.
На языке Objective-C классы и протоколы могут быть forward-declared примерно так:
@class MyClass;
@protocol MyProtocol;
На языке Objective-C классы и протоколы могут быть forward-declared, если только Вам нужно использовать их как часть типа указателя на объект, например MyClass * или id < myprotocol >. Это особенно полезно внутри определений класса, например если класс содержит члены, которые являются указателями на другие классы; чтобы избежать цикличных ссылок (например, этот класс может также содержать член, который указывает на этот же класс), мы просто вместо этого делаем предварительное декларирование классов.
Применять forward declaration для класса или протокола недостаточно, если Вам нужен подкласс subclass этого класса, или если надо реализовать этот протокол.
[Ссылка вперед (forward reference)]
Термин forward reference иногда используется как синоним forward declaration. Однако чаще это относится к реальному использованию объекта перед его определением; то есть первая ссылка над второй в коде перед объектом является forward reference. Так что можно сказать, что поскольку в Pascal обязательно нужно применить forward declaration, то forward reference в Pascal запрещены.
Пример (допустимой) forward reference на языке C++:
class C
{
public:
void mutator(int x) { myValue = x; }
int accessor() const { return myValue; }
private:
int myValue;
};
В этом примере есть две ссылки на myValue до того, как эта переменная была определена. C++ главным образом запрещает применять forward reference, но это разрешено в специальном случае для членов класса. Поскольку функция член класса accessor не может быть скомпилирована, пока компилятор не узнает тип члена класса переменной myValue, то это обязанность компилятора помнить определение accessor, пока он не увидит декларацию myValue.
Разрешение использования forward reference может значительно усложнить компиляцию и увеличить требования к памяти со стороны компилятора, и это обычно не дает реализовать компилирование за один проход.
[Ссылки]
1. Forward declaration site:en.wikipedia.org. |