Was ist der Unterschied zwischen dynamischem Versand und später Bindung in C ++?

76

Ich habe kürzlich über den dynamischen Versand auf Wikipedia gelesen und konnte den Unterschied zwischen dynamischem Versand und später Bindung in C ++ nicht verstehen.

Wann wird jeder der Mechanismen verwendet?

Das genaue Zitat aus Wikipedia:

Der dynamische Versand unterscheidet sich von der späten Bindung (auch als dynamische Bindung bezeichnet). Im Zusammenhang mit der Auswahl einer Operation bezieht sich Bindung auf den Prozess des Zuordnens eines Namens zu einer Operation. Dispatching bezieht sich auf die Auswahl einer Implementierung für die Operation, nachdem Sie entschieden haben, auf welche Operation sich ein Name bezieht. Beim dynamischen Versand kann der Name zur Kompilierungszeit an eine polymorphe Operation gebunden sein, die Implementierung kann jedoch erst zur Laufzeit ausgewählt werden (so funktioniert der dynamische Versand in C ++). Eine späte Bindung impliziert jedoch ein dynamisches Dispatching, da Sie erst auswählen können, welche Implementierung einer polymorphen Operation ausgewählt werden soll, wenn Sie die Operation ausgewählt haben, auf die sich der Name bezieht.

Vardit
quelle
2
Es ist eine gute Frage und kann besser sein, wenn Sie Links erwähnen, die Sie gelesen haben.
Masoud

Antworten:

73

Eine ziemlich anständige Antwort darauf ist tatsächlich in einer Frage zur späten oder frühen Bindung auf programmers.stackexchange.com enthalten .

Kurz gesagt, die späte Bindung bezieht sich auf die Objektseite einer Bewertung, der dynamische Versand bezieht sich auf die Funktionsseite. Bei der späten Bindung ist der Typ einer Variablen zur Laufzeit die Variante. Beim dynamischen Versand ist die ausgeführte Funktion oder Unterroutine die Variante.

In C ++ haben wir keine späte Bindung, da der Typ bekannt ist (nicht unbedingt das Ende der Vererbungshierarchie, aber zumindest eine formale Basisklasse oder Schnittstelle). Wir haben jedoch einen dynamischen Versand über virtuelle Methoden und Polymorphismus.

Das beste Beispiel, das ich für die späte Bindung anbieten kann, ist das untypisierte "Objekt" in Visual Basic. Die Laufzeitumgebung erledigt das spätbindende schwere Heben für Sie.

Dim obj

- initialize object then..
obj.DoSomething()

Der Compiler codiert tatsächlich den entsprechenden Ausführungskontext für die Laufzeit-Engine, um eine benannte Suche der aufgerufenen Methode durchzuführen DoSomething, und führt den zugrunde liegenden Aufruf tatsächlich aus, wenn er mit den ordnungsgemäß übereinstimmenden Parametern entdeckt wird. In Wirklichkeit ist etwas über den Typ des Objekts bekannt (es erbt von IDispatchund unterstützt GetIDsOfNames()usw.). Was die Sprache betrifft, ist der Typ der Variablen zur Kompilierungszeit völlig unbekannt, und es ist nicht bekannt, ob DoSomethinges sich überhaupt um eine Methode für das handelt, was objtatsächlich ist, bis die Laufzeit den Ausführungspunkt erreicht.

Ich werde mich nicht darum kümmern, eine virtuelle C ++ - Schnittstelle usw. zu sichern, da ich sicher bin, dass Sie bereits wissen, wie sie aussehen. Ich hoffe, es ist offensichtlich, dass die C ++ - Sprache dies einfach nicht kann. Es ist stark typisiert. Es kann (und tut dies natürlich) einen dynamischen Versand über die Funktion der polymorphen virtuellen Methode.

WhozCraig
quelle
2
Ich habe mein Bestes versucht, um diese Antwort zu verbessern, das erklärt tatsächlich den Unterschied. Schade, das OP wurde von 131.000 Wiederholungen geblendet - und wählte die schlechtestmögliche Antwort ...
Unsichtbarer
1
@ IInspectable es ist alles gut. Ich lasse im Allgemeinen alle Antworten, die ich poste, nach etwa einer Woche ohne Up-Votes fallen, denn wenn niemand sie hilfreich findet, möchte ich nicht, dass sie die "akzeptierten" Antworten durcheinander bringen. Aber ich bin froh, dass jemand die Erklärung des Unterschieds hier als lohnenswert empfunden hat, also werde ich sie jetzt wahrscheinlich einfach behalten. Danke für die Unterstützung.
WhozCraig
Der dynamische Versand ist nur eine bestimmte Instanz der späten Bindung, bei der der Methodenselektor der Name und die Zielmethode die zu lösende Methode ist. Der dynamische Versand ist im Grunde eine teilweise bewertete späte Bindung.
Naasking
2
@ ZanLynx sie sind anders. In VB weiß die Sprache nichts über dieses Objekt (oder dass es überhaupt ein gültiges Objekt ist), außer dass Sie (das Programm) eine aufgerufene Methode auslösen möchten DoSomthing. Verwechseln Sie die Sprache nicht mit der Laufzeit . Ich habe versucht, das klar zu machen, aber ehrlich gesagt bin ich selbst nur unwesentlich mit der Beschreibung zufrieden. Auf der C ++ - Seite müssten Sie die gesamte Dynamik selbst über codieren IDispatch, und selbst dann weiß die Sprache etwas über das Objekt (es unterstützt IDispatchusw.).
WhozCraig
1
Wäre es richtig anzunehmen, dass Java nur einen dynamischen Versand hat, da die Klasse des Objekts immer in dem Sinne bekannt ist, wie ich zitiere (nicht unbedingt das Ende der Vererbungshierarchie, sondern zumindest eine formale Basisklasse oder Schnittstelle)? . Wenn nicht, was wäre ein Beispiel für Polymorphismus?
YellowPillow
8

Bei der späten Bindung wird eine Methode zur Laufzeit namentlich aufgerufen. Sie haben dies in C ++ nicht wirklich, außer zum Importieren von Methoden aus einer DLL.
Ein Beispiel dafür wäre: GetProcAddress ()

Beim dynamischen Versand verfügt der Compiler über genügend Informationen, um die richtige Implementierung der Methode aufzurufen. Dies erfolgt normalerweise durch Erstellen einer virtuellen Tabelle .

Yochai Timmer
quelle
8

Der Link selbst erklärte den Unterschied:

Der dynamische Versand unterscheidet sich von der späten Bindung (auch als dynamische Bindung bezeichnet). Im Zusammenhang mit der Auswahl einer Operation bezieht sich Bindung auf den Prozess des Zuordnens eines Namens zu einer Operation. Dispatching bezieht sich auf die Auswahl einer Implementierung für die Operation, nachdem Sie entschieden haben, auf welche Operation sich ein Name bezieht.

und

Beim dynamischen Versand kann der Name zur Kompilierungszeit an eine polymorphe Operation gebunden sein, die Implementierung kann jedoch erst zur Laufzeit ausgewählt werden (so funktioniert der dynamische Versand in C ++). Eine späte Bindung impliziert jedoch ein dynamisches Dispatching, da Sie erst auswählen können, welche Implementierung einer polymorphen Operation ausgewählt werden soll, wenn Sie die Operation ausgewählt haben, auf die sich der Name bezieht.

In C ++ sind sie jedoch meistens gleich. Sie können einen dynamischen Versand über virtuelle Funktionen und vtables durchführen.

C ++ verwendet die frühe Bindung und bietet sowohl dynamischen als auch statischen Versand. Die Standardversandform ist statisch. Um einen dynamischen Versand zu erhalten, müssen Sie eine Methode als virtuell deklarieren.

masoud
quelle
6

In C ++ sind beide gleich.

In C ++ gibt es zwei Arten der Bindung:

  • statische Bindung - erfolgt zur Kompilierungszeit.
  • Dynamische Bindung - erfolgt zur Laufzeit.

Da die dynamische Bindung zur Laufzeit erfolgt, wird sie auch als späte Bindung bezeichnet, und die statische Bindung wird manchmal als frühe Bindung bezeichnet .

Bei Verwendung der dynamischen Bindung unterstützt C ++ den Laufzeitpolymorphismus durch virtuelle Funktionen (oder Funktionszeiger ). Bei Verwendung der statischen Bindung werden alle anderen Funktionsaufrufe aufgelöst.

Nawaz
quelle
5

Bindung bezieht sich auf den Vorgang des Zuordnens eines Namens zu einer Operation.

Hauptsache hier sind Funktionsparameter, die entscheiden, welche Funktion zur Laufzeit aufgerufen werden soll

Dispatching bezieht sich auf die Auswahl einer Implementierung für die Operation, nachdem Sie entschieden haben, auf welche Operation sich ein Name bezieht.

Versandsteuerung entsprechend der Parameterübereinstimmung

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

hoffe das hilft dir

Bhupinder
quelle
3

Lassen Sie mich Ihnen ein Beispiel für die Unterschiede geben, da sie NICHT gleich sind. Ja, beim dynamischen Versand können Sie die richtige Methode auswählen, wenn Sie von einer Oberklasse auf ein Objekt verweisen. Diese Magie ist jedoch sehr spezifisch für diese Klassenhierarchie, und Sie müssen einige Deklarationen in der Basisklasse ausführen, damit sie funktioniert (abstrakte Methoden) Füllen Sie die vtables aus, da sich der Index der Methode in der Tabelle nicht zwischen bestimmten Typen ändern kann. Sie können also Methoden in Tabby, Lion und Tiger über einen generischen Katzenzeiger aufrufen und sogar Arrays von Katzen mit Lions, Tigers und Tabbys füllen. Es weiß, auf welche Indizes sich diese Methoden zur Kompilierungszeit in der vtable des Objekts beziehen (statische / frühe Bindung), obwohl die Methode zur Laufzeit ausgewählt ist (dynamischer Versand).

Jetzt können Sie ein Array implementieren, das Löwen, Tiger und Bären enthält! ((Oh mein!)). Angenommen, wir haben keine Basisklasse namens Animal. In C ++ müssen Sie erhebliche Arbeiten ausführen, da der Compiler Sie keinen dynamischen Versand ohne eine gemeinsame Basisklasse zulässt. Die Indizes für die vtables müssen übereinstimmen, und das kann nicht zwischen nicht wiederholten Klassen durchgeführt werden. Sie benötigen eine Tabelle, die groß genug ist, um die virtuellen Methoden aller Klassen im System aufzunehmen. C ++ - Programmierer sehen dies selten als Einschränkung an, da Sie darin geschult wurden, eine bestimmte Art und Weise über das Klassendesign nachzudenken. Ich sage nicht, dass es besser oder schlechter ist.

Bei einer späten Bindung sorgt die Laufzeit dafür ohne eine gemeinsame Basisklasse. Normalerweise wird ein Hash-Tabellensystem verwendet, um Methoden in den Klassen mit einem im Dispatcher verwendeten Cache-System zu finden. In C ++ kennt der Compiler alle Typen. In einer spät gebundenen Sprache kennen die Objekte selbst ihren Typ (es ist nicht typenlos, die Objekte selbst wissen in den meisten Fällen genau, wer sie sind). Dies bedeutet, dass ich Arrays von mehreren Arten von Objekten haben kann, wenn ich möchte (Löwen und Tiger und Bären). Sie können die Nachrichtenweiterleitung und das Prototyping (ermöglicht das Ändern von Verhaltensweisen pro Objekt, ohne die Klasse zu ändern) und alle möglichen anderen Dinge auf eine Weise implementieren, die viel flexibler ist und zu weniger Code-Overhead führt als in Sprachen, die keine späte Bindung unterstützen .

Haben Sie jemals in Android programmiert und findViewById () verwendet? Sie werden fast immer das Ergebnis umwandeln, um den richtigen Typ zu erhalten, und das Umwandeln liegt im Grunde darin, den Compiler zu belügen und alle statischen Typprüfungen aufzugeben, die statische Sprachen überlegen machen sollen. Natürlich könnten Sie stattdessen findTextViewById (), findEditTextById () und eine Million andere haben, damit Ihre Rückgabetypen übereinstimmen, aber das wirft Polymorphismus aus dem Fenster; wohl die ganze Basis von OOP. In einer spät gebundenen Sprache können Sie wahrscheinlich einfach anhand einer ID indizieren und diese wie eine Hash-Tabelle behandeln, ohne sich darum zu kümmern, welcher Typ indiziert oder zurückgegeben wurde.

Hier ist ein weiteres Beispiel. Nehmen wir an, Sie haben Ihre Lion-Klasse und das Standardverhalten besteht darin, Sie zu essen, wenn Sie sie sehen. Wenn Sie in C ++ einen einzelnen "ausgebildeten" Löwen haben möchten, müssen Sie eine neue Unterklasse erstellen. Mit Prototyping können Sie einfach die eine oder zwei Methoden dieses bestimmten Löwen ändern, die geändert werden müssen. Es ist Klasse und Typ ändern sich nicht. C ++ kann das nicht. Dies ist wichtig, da Sie einen neuen "AfricanSpottedLion", der von Lion erbt, auch trainieren können. Das Prototyping ändert die Klassenstruktur nicht, sodass es erweitert werden kann. Auf diese Weise behandeln diese Sprachen normalerweise Probleme, die normalerweise eine Mehrfachvererbung erfordern, oder auf diese Weise behandeln Sie einen Mangel an Prototyping.

Zu Ihrer Information, Objective-C ist C, wobei die Nachrichtenübermittlung von SmallTalk hinzugefügt wurde, und SmallTalk ist das ursprüngliche OOP, und beide sind spät mit allen oben genannten und weiteren Funktionen verbunden. Spät gebundene Sprachen können vom Standpunkt der Mikroebene aus etwas langsamer sein, ermöglichen jedoch häufig die Strukturierung des Codes auf eine Weise, die auf Makroebene effizienter ist, und alles läuft auf die Präferenz hinaus.

Evan Langlois
quelle
2

Angesichts dieser wortreichen Wikipedia-Definition wäre ich versucht, den dynamischen Versand als die späte Bindung von C ++ zu klassifizieren

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

Eine späte Bindung scheint für Wikipedia stattdessen den Versand von C ++ von Zeiger zu Mitglied zu bedeuten

(this->*mptr)();

Dabei wird zur Laufzeit ausgewählt, welche Operation aufgerufen wird (und nicht nur welche Implementierung).

In der C ++ - Literatur wird jedoch late bindingnormalerweise für das verwendet, was Wikipedia als dynamischen Versand bezeichnet.

6502
quelle
1

Diese Frage könnte Ihnen helfen.

Dynamischer Versand bezieht sich im Allgemeinen auf Mehrfachversand.

Betrachten Sie das folgende Beispiel. Ich hoffe es könnte dir helfen.

    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 *);
};

Betrachten Sie den folgenden Fall.

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

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

Es ruft function1Einnahme Base2*nicht Derived2*. Dies ist auf das Fehlen eines dynamischen Mehrfachversands zurückzuführen.

Die späte Bindung ist einer der Mechanismen zur Implementierung eines dynamischen Einzelversands.

doptimusprime
quelle
1

Dynamischer Versand geschieht, wenn Sie das virtualSchlüsselwort in C ++ verwenden. Also zum Beispiel:

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;
}

wird gedruckt 3, da die Methode dynamisch versendet wurde . Der C ++ - Standard achtet sehr darauf, nicht genau anzugeben, wie dies hinter den Kulissen geschieht, aber jeder Compiler unter der Sonne tut dies auf die gleiche Weise. Sie erstellen eine Tabelle mit Funktionszeigern für jeden polymorphen Typ (als virtuelle Tabelle oder vtable bezeichnet ). Wenn Sie eine virtuelle Methode aufrufen, wird die "echte" Methode in der vtable nachgeschlagen und diese Version aufgerufen. Sie können sich also so etwas wie diesen Pseudocode vorstellen:

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
};

Auf diese Weise kann der Compiler sicher sein, dass zur Kompilierungszeit eine Methode mit einer bestimmten Signatur vorhanden ist. Zur Laufzeit kann der Anruf jedoch tatsächlich über die vtable an eine andere Funktion weitergeleitet werden. Aufrufe virtueller Funktionen sind aufgrund des zusätzlichen Indirektionsschritts etwas langsamer als nicht virtuelle Aufrufe.


Andererseits verstehe ich den Begriff späte Bindung so, dass der Funktionszeiger zur Laufzeit namentlich aus einer Hash-Tabelle oder ähnlichem nachgeschlagen wird . Dies ist die Art und Weise, wie Dinge in Python, JavaScript und (wenn Speicher dient) Objective-C gemacht werden. Auf diese Weise können einer Klasse zur Laufzeit neue Methoden hinzugefügt werden, was in C ++ nicht direkt möglich ist. Dies ist besonders nützlich, um Dinge wie Mixins zu implementieren. Der Nachteil ist jedoch, dass die Laufzeitsuche im Allgemeinen erheblich langsamer ist als selbst ein virtueller Aufruf in C ++, und der Compiler keine Überprüfung des Typs zur Kompilierungszeit für die neu hinzugefügten Methoden durchführen kann.

Tristan Brindle
quelle
0

Ich nehme an, die Bedeutung ist, wenn Sie zwei Klassen B haben, C die gleiche Vaterklasse A erbt, so dass der Zeiger des Vaters (Typ A) jeden der Sohntypen enthalten kann. Der Compiler kann zu einem bestimmten Zeitpunkt nicht wissen, was der Typ im Zeiger enthält, da er sich während des Programmlaufs ändern kann.

Es gibt spezielle Funktionen, um den Typ eines bestimmten Objekts zu einer bestimmten Zeit zu bestimmen. wie instanceofin Java oder von if(typeid(b) == typeid(A))...in c ++.

MeNa
quelle
0

In C ++ sind beide dynamic dispatchund late bindinggleich. Grundsätzlich bestimmt der Wert eines einzelnen Objekts den Code, der zur Laufzeit aufgerufen wird. In Sprachen wie C ++ und Java ist dynamischer Versand insbesondere dynamischer Einzelversand, der wie oben erwähnt funktioniert. In diesem Fall wird die Bindung auch aufgerufen, da sie zur Laufzeit erfolgt late binding. Sprachen wie Smalltalk ermöglichen einen dynamischen Mehrfachversand, bei dem die Laufzeitmethode zur Laufzeit basierend auf den Identitäten oder Werten von mehr als einem Objekt ausgewählt wird.

In C ++ haben wir keine späte Bindung, da die Typinformationen bekannt sind. Daher sind im C ++ - oder Java-Kontext der dynamische Versand und die späte Bindung gleich. Die tatsächliche / vollständig späte Bindung erfolgt meiner Meinung nach in Sprachen wie Python, bei denen es sich eher um eine methodenbasierte als um eine typbasierte Suche handelt.

Narr
quelle