Позднее связывание «bindLate»
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Обычный метод bind называется «ранним связыванием», поскольку фиксирует привязку сразу же.
Как только значения привязаны – они уже не могут быть изменены. В том числе, если метод объекта, который привязали, кто-то переопределит – «привязанная» функция этого не заметит.
Позднее связывание – более гибкое, оно позволяет переопределить привязанный метод когда угодно.
Раннее связывание
Например, попытаемся переопределить метод при раннем связывании:
function bind(func, context) < return function() < return func.apply(context, arguments); >; > var user = < sayHi: function() < alert('Привет!'); >> // привязали метод к объекту var userSayHi = bind(user.sayHi, user); // понадобилось переопределить метод user.sayHi = function() < alert('Новый метод!'); >// будет вызван старый метод, а хотелось бы - новый! userSayHi(); // выведет "Привет!"
…Привязка всё ещё работает со старым методом, несмотря на то что он был переопределён.
Позднее связывание
При позднем связывании bind вызовет не ту функцию, которая была в sayHi на момент привязки, а ту, которая есть на момент вызова.**
Встроенного метода для этого нет, поэтому нужно реализовать.
Синтаксис будет таков:
var func = bindLate(obj, "method");
obj Объект method Название метода (строка)
function bindLate(context, funcName) < return function() < return context[funcName].apply(context, arguments); >; >
Этот вызов похож на обычный bind , один из вариантов которого как раз и выглядит как bind(obj, «method») , но работает по-другому.
Поиск метода в объекте: context[funcName] , осуществляется при вызове, самой обёрткой.
Поэтому, если метод переопределили – будет использован всегда последний вариант.
В частности, пример, рассмотренный выше, станет работать правильно:
function bindLate(context, funcName) < return function() < return context[funcName].apply(context, arguments); >; > var user = < sayHi: function() < alert('Привет!'); >> var userSayHi = bindLate(user, 'sayHi'); user.sayHi = function() < alert('Здравствуйте!'); >userSayHi(); // Здравствуйте!
Привязка метода, которого нет
Позднее связывание позволяет привязать к объекту даже метод, которого ещё нет!
Конечно, предполагается, что к моменту вызова он уже будет определён ;).
function bindLate(context, funcName) < return function() < return context[funcName].apply(context, arguments); >; > // метода нет var user = < >; // ..а привязка возможна! var userSayHi = bindLate(user, 'sayHi'); // по ходу выполнения добавили метод.. user.sayHi = function() < alert('Привет!'); >userSayHi(); // Метод работает: Привет!
В некотором смысле, позднее связывание всегда лучше, чем раннее. Оно удобнее и надёжнее, так как всегда вызывает нужный метод, который в объекте сейчас.
Но оно влечёт и небольшие накладные расходы – поиск метода при каждом вызове.
Итого
Позднее связывание ищет функцию в объекте в момент вызова.
Оно используется для привязки в тех случаях, когда метод может быть переопределён после привязки или на момент привязки не существует.
Обёртка для позднего связывания (без карринга):
function bindLate(context, funcName) < return function() < return context[funcName].apply(context, arguments); >; >
Раннее и позднее связывание (Visual Basic)
Компилятор Visual Basic выполняет процесс, вызываемый binding при назначении объекта переменной объекта. Объект является объектом с ранним связыванием, если он присвоен переменной, объявленной с определенным типом объекта. Объекты с ранним связыванием позволяют компилятору выделять память и выполнять оптимизацию еще до запуска приложения. Например, в следующем фрагменте кода объявляется переменная типа FileStream:
' Create a variable to hold a new object. Dim FS As System.IO.FileStream ' Assign a new object to the variable. FS = New System.IO.FileStream("C:\tmp.txt", System.IO.FileMode.Open)
Так как FileStream — это тип конкретного объекта, для присвоенного переменной FS экземпляра происходит раннее связывание.
И наоборот, объект является объектом с поздним связыванием, если он присваивается переменной, объявленной с типом Object . Объекты этого типа могут содержать ссылки на любой объект, но теряют многие преимущества раннего связывания. Например, следующий фрагмент кода объявляет переменную объекта для хранения объекта, возвращаемого функцией CreateObject :
' To use this example, you must have Microsoft Excel installed on your computer. ' Compile with Option Strict Off to allow late binding. Sub TestLateBinding() Dim xlApp As Object Dim xlBook As Object Dim xlSheet As Object xlApp = CreateObject("Excel.Application") ' Late bind an instance of an Excel workbook. xlBook = xlApp.Workbooks.Add ' Late bind an instance of an Excel worksheet. xlSheet = xlBook.Worksheets(1) xlSheet.Activate() ' Show the application. xlSheet.Application.Visible = True ' Place some text in the second row of the sheet. xlSheet.Cells(2, 2) = "This is column B row 2" End Sub
Преимущества раннего связывания
Раннее связывание объектов следует использовать везде, где это возможно, поскольку оно позволяет компилятору сделать важные оптимизации, повышающие эффективность приложений. Объекты с ранним связыванием работают значительно быстрее, чем объекты с поздним связыванием. Точное указание используемых объектов позволяет упростить чтение и обслуживание кода. Еще одним преимуществом ранней привязки является включение полезных функций, таких как автоматическое завершение кода и динамическая справка, так как Visual Studio интегрированная среда разработки (IDE) может точно определить тип объекта, с которым вы работаете при редактировании кода. Раннее связывание уменьшает количество и серьезность ошибок времени выполнения, так как позволяет компилятору фиксировать многие ошибки еще при компиляции программы.
Позднее связывание применимо только для доступа к членам типа, объявленным как Public . Доступ к членам, объявленным как Friend или Protected Friend , приводит к ошибке времени выполнения.
См. также раздел
- CreateObject
- Время существования: создание и уничтожение объектов
- Object Data Type
18.5 – Раннее и позднее связывание
В этом и следующем уроках мы подробнее рассмотрим, как реализуются виртуальные функции. Хотя эта информация не является строго необходимой для эффективного использования виртуальных функций, но она интересна. Тем не менее, вы можете считать оба этих раздела необязательными для прочтения.
Когда программа на C++ выполняется, она выполняется последовательно, начиная с начала main() . Когда встречается вызов функции, точка выполнения переходит к началу вызываемой функции. Как CPU узнает об этом?
Когда программа компилируется, компилятор преобразует каждую инструкцию в вашей программе на C++ в одну или несколько строк машинного кода. Каждой строке машинного кода дается собственный уникальный адрес в последовательности адресов. То же самое и с функциями – когда встречается функция, она преобразуется в машинный код и получает следующий доступный адрес. Таким образом, каждая функция получает уникальный адрес.
Связывание относится к процессу, который используется для преобразования идентификаторов (например, имен переменных и функций) в адреса. Хотя связывание используется как для переменных, так и для функций, в этом уроке мы сосредоточимся на связывании функций.
Раннее связывание
Большинство вызовов функций, с которыми сталкивается компилятор, будут прямыми вызовами функций. Прямой вызов функции – это инструкция, которая вызывает функцию напрямую. Например:
#include void printValue(int value) < std::cout int main() < printValue(5); // это прямой вызов функции return 0; >
Прямые вызовы функций могут быть разрешены с помощью процесса, известного как раннее связывание. Раннее связывание (также называемое статическим связыванием) означает, что компилятор (или компоновщик) может напрямую связать имя идентификатора (например, имя функции или переменной) с машинным адресом. Помните, что каждая функция имеет уникальный адрес. Поэтому, когда компилятор (или компоновщик) сталкивается с вызовом функции, он заменяет вызов функции инструкцией машинного кода, которая сообщает процессору перейти к адресу функции.
Давайте посмотрим на простую программу калькулятора, в которой используется раннее связывание:
#include int add(int x, int y) < return x + y; >int subtract(int x, int y) < return x - y; >int multiply(int x, int y) < return x * y; >int main() < int x; std::cout > x; int y; std::cout > y; int op; do < std::cout > op; > while (op < 0 || op >2); int result = 0; switch (op) < // вызываем целевую функцию напрямую, используя раннее связывание case 0: result = add(x, y); break; case 1: result = subtract(x, y); break; case 2: result = multiply(x, y); break; >std::cout
Поскольку add() , subtract() и multiply() являются прямыми вызовами функций, компилятор будет использовать раннее связывание для разрешения вызовов функций add() , subtract() и multiply() . Компилятор заменит вызов функции add() инструкцией, которая сообщает процессору перейти к адресу функции add() . То же самое верно и для subtract() и multiply() .
Позднее связывание
В некоторых программах до момента выполнения (когда программа запущена) невозможно узнать, какая функция будет вызвана. Это называется поздним связыванием (или динамическим связыванием). В C++ один из способов получить позднее связывание – использовать указатели на функции. Вкратце, указатель на функцию – это тип указателя, который указывает на функцию, а не на переменную. Функция, на которую указывает указатель, может быть вызвана с помощью применения оператора вызова функции ( () ) к указателю.
Например, следующий код вызывает функцию add() :
#include int add(int x, int y) < return x + y; >int main() < // Создаем указатель на функцию и // заставляем его указывать на функцию add int (*pFcn)(int, int) = add; std::cout
Вызов функции через указатель на функцию также известен как косвенный вызов функции. Следующая программа калькулятора функционально идентична примеру калькулятора, приведенному выше, за исключением того, что в ней вместо прямого вызова функций используется указатель на функцию:
#include int add(int x, int y) < return x + y; >int subtract(int x, int y) < return x - y; >int multiply(int x, int y) < return x * y; >int main() < int x; std::cout > x; int y; std::cout > y; int op; do < std::cout > op; > while (op < 0 || op >2); // Создаем указатель на функцию с именем pFcn (да, синтаксис уродлив) int (*pFcn)(int, int) = nullptr; // Устанавливаем pFcn, чтобы указывать на функцию, которую выбрал пользователь switch (op) < case 0: pFcn = add; break; case 1: pFcn = subtract; break; case 2: pFcn = multiply; break; >// Вызов функции, на которую указывает pFcn, с параметрами x и y // Это использует позднее связывание std::cout
В этом примере вместо прямого вызова функции add() , subtract() или multiply() мы установили pFcn так, чтобы он указывал на функцию, которую мы хотим вызвать. Затем мы вызываем функцию через этот указатель. Компилятор не может использовать раннее связывание для разрешения вызова функции pFcn(x, y) , потому что во время компиляции он не может сказать, на какую функцию будет указывать pFcn !
Позднее связывание немного менее эффективно, поскольку предполагает дополнительный уровень косвенного обращения. При раннем связывании CPU может напрямую перейти к адресу функции. При позднем связывании программа должна сначала прочитать адрес, содержащийся в указателе, а затем перейти к этому адресу. Это включает в себя один дополнительный шаг, что делает этот тип связывания немного медленнее. Однако преимущество позднего связывания состоит в том, что оно гибче, чем раннее связывание, поскольку решения о том, какую функцию вызывать, не нужно принимать до времени выполнения.
В следующем уроке мы рассмотрим, как позднее связывание используется для реализации виртуальных функций.
Раннее и позднее связывание
Имеются два термина, часто используемых, когда речь заходит об объектно-ориентированных языках программирования: раннее и позднее связывание. По отношению к С++ эти термины соответствуют событиям, которые возникают на этапе компиляции и на этапе исполнения программы соответственно.
В терминах объектно-ориентированного программирования раннее связывание означает, что объект и вызов функции связываются между собой на этапе компиляции. Это означает, что вся необходимая информация для того, чтобы определить, какая именно функция будет вызвана, известна на этапе компиляции программы. В качестве примеров раннего связывания можно указать стандартные вызовы функций, вызовы перегруженных функций и перегруженных операторов. Принципиальным достоинством раннего связывания является его эффективность — оно более быстрое и обычно требует меньше памяти, чем позднее связывание. Его недостатком служит невысокая гибкость.
Позднее связывание означает, что объект связывается с вызовом функции только во время исполнения программы, а не раньше. Позднее связывание достигается в С++ с помощью использования виртуальных функций и производных классов. Его достоинством является высокая гибкость. Оно может использоваться для поддержки общего интерфейса, позволяя при этом различным объектам иметь свою собственную реализацию этого интерфейса. Более того, оно помогает создавать библиотеки классов, допускающие повторное использование и расширение.
Какое именно связывание должна использовать программа, зависит от предназначения программы. Фактически достаточно сложные программы используют оба вида связывания. Позднее связывание является одним из самых мощных добавлений языка С++ к возможностям языка С. Платой за такое увеличение мощи программы служит некоторое уменьшение ее скорости исполнения. Поэтому использование позднего связывания оправдано только тогда, когда оно улучшает структурированность и управляемость программы. Надо иметь в виду, что проигрыш в производительности невелик, поэтому когда ситуация требует позднего связывания, можно использовать его без всякого сомнения.