В вычислительной технике позднее связывание или динамическое связывание [1] — хотя это и не идентичный процесс динамическому связыванию импортированных библиотек кода — это механизм компьютерного программирования , в котором метод, вызываемый для объекта , или функция , вызываемая с аргументами, ищется по имени во время выполнения . Другими словами, имя связывается с определенной операцией или объектом во время выполнения, а не во время компиляции . Иногда используется название динамическое связывание , [2] , но чаще используется для обозначения динамической области действия .
При раннем связывании или статическом связывании в объектно-ориентированном языке фаза компиляции фиксирует все типы переменных и выражений . Обычно это сохраняется в скомпилированной программе как смещение в таблице виртуальных методов («v-table»). [3] Напротив, при позднем связывании компилятор не считывает достаточно информации, чтобы проверить существование метода или связать его слот с v-table. Вместо этого метод ищется по имени во время выполнения.
Основное преимущество использования позднего связывания в программировании Component Object Model (COM) заключается в том, что компилятору не требуется ссылаться на библиотеки, содержащие объект во время компиляции . Это делает процесс компиляции более устойчивым к конфликтам версий, в которых v-таблица класса может быть случайно изменена. (Это не проблема для платформ с компиляцией just-in-time , таких как .NET или Java , поскольку v-таблица создается во время выполнения виртуальной машиной для библиотек, когда они загружаются в работающее приложение. [4] )
Термин «позднее связывание» появился как минимум в 1960-х годах, его можно найти в Communications of the ACM . Этот термин широко использовался для описания соглашений о вызовах в таких языках, как Lisp, хотя обычно с негативным подтекстом относительно производительности. [5]
В 1980-х годах Smalltalk популяризировал объектно-ориентированное программирование (ООП) и вместе с ним позднее связывание. Алан Кей однажды сказал: «ООП для меня означает только обмен сообщениями, локальное сохранение, защиту и сокрытие состояния процесса и крайне позднее связывание всех вещей. Это можно сделать в Smalltalk и в LISP. Возможно, есть и другие системы, в которых это возможно, но я о них не знаю». [6]
В начале-середине 1990-х годов Microsoft активно продвигала свой стандарт COM как бинарный интерфейс между различными языками программирования ООП. Программирование COM в равной степени продвигало раннее и позднее связывание, причем многие языки поддерживали оба на уровне синтаксиса.
В 2000 году Алекс Мартелли ввел термин « утиная типизация » для обозначения похожей концепции, но с другим акцентом. В то время как позднее связывание обычно фокусируется на деталях реализации, утиная типизация фокусируется на способности игнорировать типы и концентрироваться на методах, которые в данный момент есть у объекта.
В большинстве динамически типизированных языков список методов объекта может быть изменен во время выполнения. Для этого требуется позднее связывание.
В Lisp глобальные вызовы функций с поздним связыванием эффективно ищутся во время выполнения через функциональную ячейку символа . Эти привязки функций изменяемы.
Пример использования интерактивного сеанса Clozure Common Lisp :
? ( defun foo () ( bar pi )) ; вызывается все еще неопределенная функция BAR ;Предупреждения компилятора: ; В FOO: Неопределенная функция BAR FOO ? ( defun bar ( x ) ; теперь мы его определяем ( * x 2 )) BAR ? ( foo ) ; вызывает foo и использует последнее определение BAR 6.283185307179586D0 ? ( defun bar ( x ) ; теперь мы переопределяем BAR ( * x 1000 )) BAR ? ( foo ) ; FOO теперь вызывает новую функцию, нет необходимости перекомпилировать/линковать/загружать FOO 3141.592653589793D0 ? ( тип-из 'bar ) ; BAR - это символ SYMBOL ? ( symbol-function 'bar ) ; символ BAR имеет привязку к функции # <Compiled-function BAR #x302000D1B21F >
В C++ позднее связывание (также называемое «динамическим связыванием») относится к тому, что обычно происходит, когда virtual
ключевое слово используется в объявлении метода. Затем C++ создает так называемую виртуальную таблицу , которая является таблицей поиска для таких функций, которые всегда будут использоваться при их вызове. [7] Обычно термин «позднее связывание» используется вместо термина « динамическая диспетчеризация ».
В программировании COM вызов метода с поздним связыванием выполняется с использованием интерфейса IDispatch . Некоторые языки на основе COM, такие как Visual Basic 6, имеют синтаксическую поддержку для вызова этого интерфейса. [8] Это делается путем определения типа переменной как Object. Другие, такие как C++, требуют, чтобы вы явно вызывали GetIDsOfNames для поиска метода и Invoke для его вызова.
В .NET позднее связывание относится к переопределению метода virtual
, например C++, или реализации интерфейса. Компилятор создает виртуальные таблицы для каждого вызова виртуального или интерфейсного метода, который используется во время выполнения для определения реализации для выполнения.
Также как COM и Java, Common Language Runtime предоставляет API рефлексии, которые могут выполнять вызовы позднего связывания. Использование этих вызовов зависит от языка.
С C# 4 язык также добавил псевдотип "dynamic". Он будет использоваться вместо типа Object, чтобы указать, что желательно позднее связывание. Конкретный необходимый механизм позднего связывания определяется во время выполнения с использованием Dynamic Language Runtime в качестве отправной точки.
Visual Basic использует их всякий раз, когда переменная имеет тип Object и действует директива компилятора "Option Strict Off". Это настройка по умолчанию для нового проекта VB. До версии 9 только объекты .NET и COM могли быть связаны с поздним связыванием. С VB 10 это было расширено на объекты на основе DLR.
В Java существует три определения позднего связывания.
В ранних документах по Java обсуждалось, как классы не были связаны друг с другом во время компиляции. Хотя типы статически проверяются во время компиляции, различные реализации классов можно было заменить непосредственно перед выполнением, просто перезаписав файл класса. Пока новое определение класса имело те же имена классов и методов, код все равно работал. В этом смысле это похоже на традиционное определение позднего связывания.
В настоящее время в программировании на Java популярно использовать термин позднее связывание как синоним динамической диспетчеризации . В частности, это относится к механизму единой диспетчеризации Java, используемому с виртуальными методами.
Наконец, Java может использовать позднее связывание, используя свои API рефлексии и интроспекцию типов, во многом так же, как это делается в программировании COM и .NET. Вообще говоря, те, кто программирует только на Java, не называют это поздним связыванием. Аналогично, использование методов "утиной типизации" не приветствуется в программировании на Java, вместо этого используются абстрактные интерфейсы.
Oracle, нынешний владелец Java, известен тем, что использует термин позднее связывание в смысле «утиной типизации» при обсуждении Java и других языков в одной и той же документации. [9]
При использовании раннего связывания между Ada и хранимой в базе данных процедурой проверяется временная метка, чтобы убедиться, что хранимая процедура не изменилась с момента компиляции кода. Это позволяет ускорить выполнение и предотвращает запуск приложения с неправильной версией хранимой процедуры. [10]
При использовании позднего связывания проверка временной метки не выполняется, а хранимая процедура выполняется через анонимный блок PL/SQL. Хотя это может быть медленнее, это устраняет необходимость перекомпиляции всех клиентских приложений при изменении хранимой процедуры.
Это различие, по-видимому, уникально для PL/SQL и Ada. Другие языки, которые могут вызывать процедуры PL/SQL, а также другие движки баз данных используют только позднее связывание.
Позднее связывание имеет худшую производительность, чем вызов метода с ранним связыванием. В большинстве реализаций правильный адрес метода должен искаться по имени при каждом вызове, что требует относительно дорогого поиска по словарю и, возможно, логики разрешения перегрузки. В большинстве приложений дополнительные вычисления и требуемое время незначительны на современных компьютерах.
Для некоторых компиляторов позднее связывание может помешать использованию статической проверки типов. При вызове позднего связывания компилятор должен предположить, что метод существует. Это означает, что простая орфографическая ошибка может привести к возникновению ошибки времени выполнения. Современные компиляторы избегают этого, гарантируя, что каждый возможный вызов должен иметь реализацию во время компиляции.
Позднее связывание может помешать формам статического анализа, необходимым интегрированной среде разработки (IDE). Например, функция IDE «перейти к определению» может не работать при вызове с поздним связыванием, если IDE не может узнать, к какому классу может относиться вызов. Современная IDE легко решает эту проблему, особенно для объектно-ориентированных языков, поскольку метод с поздним связыванием всегда указывает интерфейс или базовый класс, куда ведет «перейти к определению», а «найти все ссылки» можно использовать для поиска всех реализаций или переопределений. [11]