У чому різниця між динамічним відправленням та пізнім прив’язуванням у C ++?


76

Нещодавно я читав про динамічне відправлення у Вікіпедії і не міг зрозуміти різницю між динамічним відправленням та пізнім прив'язуванням у C ++.

Коли використовується кожен з механізмів?

Точна цитата з Вікіпедії:

Динамічне відправлення відрізняється від пізнього прив'язки (також відоме як динамічне прив'язка). У контексті вибору операції прив'язка відноситься до процесу асоціювання імені з операцією. Диспетчеризація стосується вибору реалізації для операції після того, як ви вирішили, до якої операції відноситься назва. При динамічному відправленні ім'я може бути прив'язане до поліморфної операції під час компіляції, але реалізація не вибирається до часу виконання (саме так працює динамічна відправка в C ++). Однак пізнє прив'язування означає динамічну диспетчеризацію, оскільки ви не можете вибрати, яку реалізацію поліморфної операції вибрати, поки не вибрали операцію, на яку посилається назва.


2
Це гарне запитання, і це може бути краще, якщо ви згадаєте прочитані посилання.
masoud

Відповіді:


73

Досить пристойну відповідь на це насправді включено у питання про пізнє та раннє прив’язку до programmers.stackexchange.com .

Коротше кажучи, пізня прив'язка відноситься до сторони об'єкта , а динамічна відправка - до функціональної сторони. При пізньому прив'язуванні типом змінної є варіант під час виконання. У динамічному відправленні функцією або підпрограмою, що виконується, є варіант.

У C ++ ми насправді не маємо пізнього прив'язки, оскільки тип відомий (не обов'язково кінець ієрархії успадкування, але принаймні офіційний базовий клас або інтерфейс). Але ми робимо є динамічна диспетчеризація з допомогою віртуальних методів і поліморфізму.

Найкращий приклад, який я можу запропонувати для пізнього прив'язки - це нетипізований "об'єкт" у Visual Basic. Середовище виконання виконує всі важкі важкі дії, що стосуються пізнього зв’язку.

Dim obj

- initialize object then..
obj.DoSomething()

Компілятор фактично кодує відповідний контекст виконання для середовища виконання, щоб виконати іменований пошук викликаного методу DoSomething, і якщо виявлений із належним чином відповідними параметрами, фактично виконає базовий виклик. Насправді відомо щось про тип об'єкта (він успадковує IDispatchта підтримує GetIDsOfNames()тощо). але що стосується мови , то тип змінної абсолютно невідомий під час компіляції, і він не уявляє, чи DoSomethingє це навіть методом для того, що objнасправді є, поки час виконання не досягне точки виконання.

Я не буду турбуватися про скидання віртуального інтерфейсу C ++ et'al, оскільки я впевнений, ви вже знаєте, як вони виглядають. Сподіваюся, очевидно, що мова С ++ просто не може цього зробити. Він сильно набраний. Він може (і, очевидно, робить) динамічне відправлення за допомогою функції поліморфного віртуального методу.


2
Я намагався з усіх сил проголосувати цю відповідь, яка насправді пояснює різницю. Шкода, ОП був засліплений репутацією 131 тис. - і обрав найгіршу можливу відповідь ...
IIСпецифічно

1
@IInspectable все добре. Я зазвичай скидаю будь-які відповіді, які я публікую, не отримуючи жодних голосів після того, як пройде тиждень, або близько того, тому що, якщо ніхто не вважає їх корисними, я не хочу, щоб вони застрягли, захаращуючи "прийняті" відповіді. Але я радий, що хтось знайшов тут пояснення різниці вартим уваги, тому я, швидше за все, просто збережу його зараз. Дякую за підтримку.
WhozCraig

Динамічне відправлення - це лише окремий екземпляр пізнього прив'язки, де селектор методів - це ім'я, а цільовий метод - той, який потрібно вирішити. Динамічне відправлення в основному є частково оціненим пізнім зв'язуванням.
naasking

2
@ZanLynx вони різні. У В.Б. Langauge нічого не знає про цей об'єкт (або , що навіть дійсний об'єкт на всіх), крім вас (програми) хоче звільнити метод , званий DoSomthing. Не плутайте мову з робочим середовищем . Я намагався це пояснити, але, чесно кажучи, я сам лише незначно задоволений описом. На стороні С ++ вам доведеться кодувати всю динаміку самостійно IDispatch, і навіть тоді мова знає щось про об'єкт (він підтримує IDispatchтощо).
WhozCraig

1
Чи правильно би я припустив, що Java має лише динамічну диспетчеризацію, оскільки клас Об'єкта завжди відомий у тому значенні, і я цитую (не обов'язково кінець ієрархії успадкування, але принаймні офіційний базовий клас або інтерфейс) . Якщо ні, то що було б прикладом поліморфізму?
YellowPillow

8

Пізнє прив'язування - це виклик методу за іменем під час виконання. У вас насправді цього немає в C ++, за винятком імпорту методів з DLL.
Прикладом для цього може бути: GetProcAddress ()

При динамічному відправленні компілятор має достатньо інформації, щоб викликати правильну реалізацію методу. Зазвичай це робиться шляхом створення віртуальної таблиці .


8

Посилання сама по собі пояснює різницю:

Динамічне відправлення відрізняється від пізнього прив'язки (також відоме як динамічне прив'язка). У контексті вибору операції прив'язка відноситься до процесу асоціювання імені з операцією. Диспетчеризація стосується вибору реалізації для операції після того, як ви вирішили, до якої операції відноситься назва.

і

При динамічному відправленні ім'я може бути прив'язане до поліморфної операції під час компіляції, але реалізація не вибирається до часу виконання (саме так працює динамічна відправка в C ++). Однак пізнє прив'язування означає динамічну диспетчеризацію, оскільки ви не можете вибрати, яку реалізацію поліморфної операції вибрати, поки не вибрали операцію, на яку посилається назва.

Але вони в основному рівні в C ++, ви можете динамічно відправляти віртуальні функції та vtables.

C ++ використовує раннє прив'язку та пропонує як динамічну, так і статичну відправку. За замовчуванням форма відправлення - статична. Для отримання динамічного відправлення потрібно оголосити метод віртуальним.


6

У C ++ обидва вони однакові.

У C ++ існує два типи прив'язки:

  • статичне прив'язування - яке виконується під час компіляції.
  • динамічне прив'язування - яке робиться під час виконання.

Динамічне прив'язування, оскільки воно виконується під час виконання, також називають пізнім прив'язуванням, а статичне прив'язування часом називають раннє прив'язку .

Використовуючи динамічне прив'язування, C ++ підтримує поліморфізм виконання за допомогою віртуальних функцій (або покажчиків на функції ), а за допомогою статичного прив'язки вирішуються всі інші виклики функцій.


5

Прив’язка відноситься до процесу прив’язки імені до операції.

тут головне - це параметри функції, які вирішують, яку функцію викликати під час виконання

Диспетчеризація стосується вибору реалізації для операції після того, як ви вирішили, до якої операції відноситься назва.

керування відправленням відповідно до відповідності параметрів

http://en.wikipedia.org/wiki/Dynamic_dispatch

сподіваюся, це допоможе вам


3

Дозвольте навести вам приклад відмінностей, оскільки вони НЕ однакові. Так, динамічне відправлення дозволяє вибрати правильний метод, коли ви посилаєтесь на об'єкт суперкласом, але ця магія дуже специфічна для цієї ієрархії класів, і вам потрібно зробити деякі оголошення в базовому класі, щоб він працював (абстрактні методи заповнити vtables, оскільки індекс методу в таблиці не може змінюватися між конкретними типами). Отже, ви можете викликати методи в Tabby і Lion і Tiger за допомогою загального вказівника Cat і навіть мати масиви Кішок, наповнених Lions and Tigers and Tabbys. Він знає, на які індекси посилаються ці методи у vtable об'єкта під час компіляції (статичне / раннє прив'язка), навіть незважаючи на те, що метод обраний під час виконання (динамічне відправлення).

Тепер давайте реалізуємо масив, який містить Левів, Тигрів та Ведмедів! ((О мій!)). Якщо припустити, що у нас немає базового класу під назвою Animal, то в C ++ вам доведеться зробити значну роботу, тому що компілятор не дозволить вам виконувати будь-які динамічні відправлення без загального базового класу. Індекси для vtables повинні збігатися, і цього неможливо зробити між неповторними класами. Вам потрібно мати достатньо великий vtable для зберігання віртуальних методів усіх класів у системі. Програмісти на C ++ рідко сприймають це як обмеження, оскільки вас навчили певним чином думати про дизайн класу. Я не кажу, що це краще чи гірше.

При пізньому прив'язуванні час виконання подбає про це без загального базового класу. Зазвичай існує система хеш-таблиць, яка використовується для пошуку методів у класах із системою кешування, що використовується в диспетчері. Де в C ++, компілятор знає всі типи. У мові з пізньою прив'язкою самі предмети знають свій тип (він не безтиповий, самі об'єкти точно знають, ким вони є в більшості випадків). Це означає, що я можу мати масиви різних типів об’єктів, якщо хочу (Леви, Тигри та Ведмеді). І ви можете реалізувати пересилання та прототипування повідомлень (дозволяє змінювати поведінку кожного об’єкта без зміни класу) та всілякі інші речі набагато більш гнучкими та призводять до менших витрат коду, ніж у мовах, які не підтримують пізнє прив’язування .

Ви коли-небудь програмували на Android і використовували findViewById ()? Ви майже завжди в кінцевому підсумку віддаєте результат, щоб отримати правильний тип, і кастинг в основному бреше компілятору і відмовляється від усієї статичної перевірки доброти, яка повинна зробити статичні мови вищими. Звичайно, ви могли б замість цього знайти findTextViewById (), findEditTextById () та мільйон інших, щоб ваші типи повернення збігалися, але це викидання поліморфізму у вікно; можливо ціла основа ООП. Мова з пізньою прив'язкою, ймовірно, дозволить вам просто індексувати ідентифікатор і поводитися з нею як з хеш-таблицею і не хвилювати, який тип індексується і не повертається.

Ось ще один приклад. Скажімо, у вас є клас Лев, і його поведінка за замовчуванням з’їдає вас, коли ви його бачите. У C ++, якщо ви хотіли мати одного "навченого" лева, вам потрібно скласти новий підклас. Прототипування дозволить вам просто змінити один або два методи конкретного Лева, які потрібно змінити. Це клас і тип не змінюються. C ++ не може цього зробити. Це важливо, оскільки коли у вас є новий "AfricanSpottedLion", який успадковується від Lion, ви можете його також навчити. Прототипування не змінює структуру класу, тому його можна розширити. Як правило, ці мови обробляють проблеми, які зазвичай вимагають багаторазового успадкування, або, можливо, множинне успадкування - це спосіб вирішення проблеми з відсутністю прототипування.

FYI, Objective-C - це C з доданим повідомленням про передачу SmallTalk, а SmallTalk - оригінальним ООП, і обидва пізно пов'язані з усіма описаними вище функціями. Мови з пізньою прив'язкою можуть бути трохи повільнішими з точки зору мікрорівню, але часто можуть дозволити структуру коду таким чином, щоб він був більш ефективним на макрорівні, і все зводиться до переваг.


2

Враховуючи це багатозначне визначення у Вікіпедії, я мав би спокусу класифікувати динамічне відправлення як пізнє прив'язування C ++

struct Base {
    virtual void foo(); // Dynamic dispatch according to Wikipedia definition
    void bar();         // Static dispatch according to Wikipedia definition
};

Натомість пізнє прив'язування для Вікіпедії, мабуть, означає відправлення C ++ від вказівника до члена

(this->*mptr)();

де вибір того, до якої операції викликається (а не лише якої реалізації), робиться під час виконання.

Однак у літературі C ++ література late bindingзазвичай використовується для того, що Вікіпедія називає динамічним відправленням.


1

Це питання може вам допомогти.

Динамічне відправлення, як правило, відноситься до багаторазового відправлення.

Розглянемо наведений нижче приклад. Сподіваюся, це може вам допомогти.

    class Base2;
    class Derived2; //Derived2 class is child of Base2
class Base1 {
    public:
        virtual void function1 (Base2 *);
        virtual void function1 (Derived2 *);
}

class Derived1: public Base1 {
    public:
    //override.
    virtual void function1(Base2 *);
    virtual void function1(Derived2 *);
};

Розглянемо випадок нижче.

Derived1 * d = new Derived1;
Base2 * b = new Derived2;

//Now which function1 will be called.
d->function1(b);

Це буде називати function1прийом Base2*не Derived2*. Це пов'язано з відсутністю динамічного багаторазового відправлення.

Пізнє прив'язування є одним із механізмів реалізації динамічної разової диспетчеризації.


1

Динамічне відправлення - це те, що відбувається, коли ви використовуєте virtualключове слово в C ++. Так наприклад:

struct Base
{
    virtual int method1() { return 1; }
    virtual int method2() { return 2; } // not overridden
};

struct Derived : public Base
{
    virtual int method1() { return 3; }
}

int main()
{
    Base* b = new Derived;
    std::cout << b->method1() << std::endl;
}

надрукує 3, оскільки метод динамічно відправлявся . Стандарт C ++ дуже обережний , НЕ вказує, як саме це відбувається за лаштунками, але кожен компілятор під сонцем робить це однаково. Вони створюють таблицю покажчиків на функції для кожного поліморфного типу (так звана віртуальна таблиця або vtable ), і коли ви викликаєте віртуальний метод, "справжній" метод шукається з vtable і викликається ця версія. Тож ви можете зобразити щось на зразок цього псевдокоду:

struct BaseVTable
{
    int (*_method1) () = &Base::method1; // real function address
    int (*_method2) () = &Base::method2;
};

struct DerivedVTable
{  
    int (*method) () = &Derived::method1;
    int (*method2) () = &Base::method2; // not overridden
};

Таким чином, компілятор може бути впевнений, що метод із певним підписом існує під час компіляції. Однак під час виконання виклик може фактично бути відправлений через vtable до іншої функції. Дзвінки до віртуальних функцій є трохи повільнішими, ніж невіртуальні дзвінки, через додатковий крок опосередкування.


З іншого боку, я розумію термін пізнє прив'язування , що покажчик функції шукається по імені під час виконання, з хеш-таблиці або чогось подібного. Це те, як все робиться в Python, JavaScript та (якщо пам'ять не зраджує) Objective-C. Це дає можливість додавати нові методи до класу під час виконання , чого неможливо зробити безпосередньо в C ++. Це особливо корисно для реалізації таких речей, як міксини. Однак недоліком є ​​те, що пошук під час виконання, як правило, значно повільніший, ніж навіть віртуальний виклик у C ++, і компілятор не може виконувати жодної перевірки типу компіляції для нещодавно доданих методів.


0

Я припускаю, що сенс полягає в тому, що у вас є два класи B, C успадковує один і той же клас батька A. Отже, покажчик батька (тип A) може містити кожен із типів синів. Компілятор не може знати, який тип утримується в покажчику за певний час, оскільки він може змінюватися під час запуску програми.

Існують спеціальні функції для визначення типу певного об'єкта в певний час. як instanceofу java або if(typeid(b) == typeid(A))...в c ++.


0

У C ++ обидва dynamic dispatchі late bindingоднакові. В основному, значення одного об'єкта визначає фрагмент коду, який викликається під час виконання. У таких мовах, як C ++ та Java, динамічне відправлення - це більш конкретно динамічне одиночне відправлення, яке працює, як зазначено вище. У цьому випадку, оскільки зв'язування відбувається під час виконання, воно також називаєтьсяlate binding . Такі мови, як smalltalk, дозволяють здійснювати динамічну багаторазову диспетчеризацію, в якій метод виконання обирається під час виконання на основі ідентифікаційних даних або значень більш ніж одного об'єкта.

У C ++ насправді не існує пізнього прив'язки, оскільки інформація про тип відома. Таким чином, у контексті C ++ або Java динамічне відправлення та пізнє прив'язування однакові. Фактичне / повністю пізнє прив'язування, я думаю, це в таких мовах, як python, який є методом пошуку, а не типом.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.