C ++中的动态分派和后期绑定之间有什么区别?


76

我最近在Wikipedia上阅读了有关动态调度的内容,但无法理解C ++中动态调度和后期绑定之间的区别。

什么时候使用每种机制?

维基百科的确切报价:

动态分派不同于后期绑定(也称为动态绑定)。在选择操作的上下文中,绑定是指将名称与操作相关联的过程。调度是指在您决定名称所指的是哪个操作之后,为该操作选择一个实现。使用动态调度,可以在编译时将名称绑定到多态操作,但是直到运行时才选择实现(这是C ++中动态调度的工作方式)。但是,后期绑定确实意味着动态分派,因为在选择名称所指代的操作之前,您无法选择要选择的多态操作的实现。


2
这是一个很好的问题,如果提及已阅读的链接,可能会更好。
masoud 2013年

Answers:


73

一个相当不错的答案,这实际上是纳入在后期与早期绑定一个问题programmers.stackexchange.com

简而言之,后期绑定是指评估的对象侧,动态调度是指功能侧。在后期绑定中,变量的类型是运行时的变量。在动态调度中,正在执行的函数或子例程就是变量。

在C ++中,因为类型是已知的,所以我们实际上并没有后期绑定(不一定是继承层次结构的末尾,而是至少是正式的基类或接口)。但是我们确实通过虚拟方法和多态性进行了动态调度。

我可以提供的后期绑定的最佳示例是Visual Basic中的无类型“对象”。运行时环境为您完成了所有后期绑定繁重的工作。

Dim obj

- initialize object then..
obj.DoSomething()

编译器将为运行时引擎实际编写适当的执行上下文,以执行名为的方法的命名查找DoSomething,并且如果发现了具有正确匹配的参数,则将实际执行基础调用。实际上,关于对象类型的某些信息是已知的(它继承自IDispatch并支持GetIDsOfNames(),等等)。但至于语言来讲类型的变量是在编译时完全未知的,如果它不知道DoSomething,甚至无论什么方法obj实际上直到运行时到达执行点。

我不会打扰C ++虚拟接口等,因为我确信您已经知道它们的外观。我希望很明显C ++语言无法做到这一点。它是强类型的。它可以(并且确实)通过多态虚拟方法功能进行动态调度。


2
我尽力支持这个答案,这实际上解释了区别。太糟糕了,OP被131k rep蒙蔽了眼睛-并选择了最糟糕的答案...
IInspectable

1
@IInspectable一切都很好。我通常会在一周左右的时间里放弃我发布的任何答案,而没有上投票,因为如果没人发现他们有帮助,那么我就不想让他们陷入混乱。但是我很高兴有人在这里找到了差异说明,所以现在我可能会保留它。感谢您的支持。
WhozCraig

动态分配只是延迟绑定的一个特殊实例,其中方法选择器是名称,而目标方法是要解析的方法。动态调度基本上是部分评估的后期绑定。
naasking

2
@ZanLynx他们是不同的。在VB中,除了您(程序)想要触发一个称为的方法外,语言对该对象(甚至根本不是一个有效对象一无所知DoSomthing。不要将语言运行时混淆。我试图弄清楚这一点,但老实说,我本人仅对描述感到满意。在C ++方面,必须自己通过编写所有动态代码IDispatch,即使这样,该语言也知道对象的某些内容(它支持IDispatch等)。
WhozCraig

1
我认为Java仅具有动态分派是正确的,因为在这样的意义上总是知道Object的类,并且我引用(不一定是继承层次结构的末尾,而是至少是正式的基类或接口) 。如果不是,那么多态的例子将是什么?
YellowPillow 2015年

8

后期绑定是在运行时按名称调用方法。除了从DLL导入方法外,在c ++中实际上没有此功能。
例如:GetProcAddress()

使用动态分派,编译器具有足够的信息来调用该方法的正确实现。这通常是通过创建虚拟表来完成的。


8

链接本身说明了区别:

动态分派不同于后期绑定(也称为动态绑定)。在选择操作的上下文中,绑定是指将名称与操作相关联的过程。调度是指在您决定名称所指的是哪个操作之后,为该操作选择一个实现。

使用动态分派,可以在编译时将名称绑定到多态操作,但是直到运行时才选择实现(这是C ++中动态分派的工作方式)。但是,后期绑定确实意味着动态分派,因为在选择名称所指代的操作之前,您无法选择要选择的多态操作的实现。

但是它们在C ++中几乎是平等的,您可以通过虚拟函数和vtables进行动态分配。

C ++使用早期绑定,并提供动态和静态分派。调度的默认形式是静态的。要获得动态调度,必须将一个方法声明为虚方法。


6

在C ++中,两者是相同的。

在C ++中,有两种绑定:

  • 静态绑定-在编译时完成。
  • 动态绑定-在运行时完成。

由于动态绑定是在运行时完成的,因此也称为后期绑定,而静态绑定有时又称为早期绑定

使用动态绑定,C ++通过虚拟函数(或函数指针)支持运行时多态性,而使用静态绑定,则可以解决所有其他函数调用。


5

绑定是指将名称与操作相关联的过程。

这里最主要的是函数参数,这些参数决定了在运行时调用哪个函数

调度是指在您决定名称所指的操作之后,为该操作选择一个实现。

根据参数匹配将控制权分配给它

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

希望这对你有帮助


3

让我举一个例子说明这些差异,因为它们并不相同。是的,当您通过超类引用对象时,动态分派可以让您选择正确的方法,但是这种魔术是特定于该类层次结构的,您必须在基类中进行一些声明才能使其起作用(抽象方法填写vtable,因为表中方法的索引不能在特定类型之间改变)。因此,您可以通过通用的Cat指针在Tabby,Lion和Tiger中调用所有方法,甚至可以将填充有Lions,Tigers和Tabbys的Cat数组。它知道这些方法在编译时(静态/早期绑定)在对象的vtable中引用了哪些索引,即使该方法是在运行时选择的(动态分配)也是如此。

现在,让我们实现一个包含狮子,老虎和熊的数组!((天啊!))。假设在C ++中没有名为Animal的基类,您将需要做大量工作,因为在没有通用基类的情况下,编译器将不允许您进行任何动态分配。vtable的索引需要匹配,而这不能在未发布的类之间完成。您需要有一个足够大的vtable来容纳系统中所有类的虚拟方法。C ++程序员很少将此视为限制,因为您已经受过训练,可以思考有关类设计的某种方式。我并不是说它的好坏。

通过后期绑定,运行时无需通用的基类即可解决此问题。通常有一个哈希表系统,用于在类中查找方法,而调度程序中使用的是缓存系统。在C ++中,编译器知道所有类型。在后期绑定语言中,对象本身知道其类型(不是无类型的,大多数情况下对象本身都知道它们是谁)。这意味着我可以根据需要拥有多种类型的对象的数组(狮子,老虎和熊)。而且,您可以实现消息转发和原型制作(允许在不更改类的情况下更改每个对象的行为)和各种其他方式,与不支持后期绑定的语言相比,这种方式更加灵活并且可以减少代码开销。

曾经在Android中编程并使用findViewById()吗?您几乎总是最终将结果转换为正确的类型,而转换基本上是编译器的谎言,并放弃了所有应该使静态语言更优越的静态类型检查优势。当然,您可以改为使用findTextViewById(),findEditTextById()以及其他一百万个,以便您的返回类型匹配,但是这将多态性抛在了窗外。可以说是OOP的全部基础。后期绑定的语言可能会让您简单地按ID索引,并将其视为哈希表,而不管正在索引或返回什么类型。

这是另一个例子。假设您拥有Lion类,并且它的默认行为是在看到它时就吃饭。在C ++中,如果您想拥有一头“经过训练的”狮子,则需要创建一个新的子类。原型制作使您可以简单地更改需要更改的特定Lion的一种或两种方法。它的类和类型不变。C ++无法做到这一点。这很重要,因为当您有一个继承自Lion的新“ AfricanSpottedLion”时,您也可以对其进行培训。原型不会改变类的结构,因此可以进行扩展。通常,这是这些语言处理通常需要多重继承的问题的方式,或者也许多重继承是您处理缺乏原型的方式。

仅供参考,Objective-C是C,其中添加了SmallTalk的消息传递功能,而SmallTalk是原始的OOP,并且两者均受以上所有功能的约束。从微观级别的角度来看,后期绑定语言可能会稍微慢一些,但通常可以使代码以在宏级别上更高效的方式进行结构化,并且都归结为优先选择。


2

鉴于冗长的Wikipedia定义,我很想将动态分派归类为C ++的后期绑定

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

相反,对于Wikipedia而言,后期绑定似乎意味着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中查找“ real”方法,然后调用该版本。因此,您可以想象类似以下伪代码的内容:

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 ++中是无法直接完成的。这对于实现诸如mixin之类的东西特别有用。但是,缺点是运行时查找通常比C ++中的虚拟调用要慢得多,并且编译器无法对新添加的方法执行任何编译时类型检查。


0

我想的意思是,当您有两个类B,C继承同一个父亲类A。因此,父亲的指针(类型A)可以容纳每个儿子类型。编译器无法在特定时间内知道指针中包含的类型,因为它可以在程序运行期间更改。

有一些特殊功能可以确定特定对象在特定时间内的类型。就像instanceof在Java或if(typeid(b) == typeid(A))...C ++中一样。


0

在C ++中,两者dynamic dispatchlate binding是相同的。基本上,单个对象的值确定运行时调用的代码段。在像C ++和Java这样的语言中,动态分配更具体地讲是如上所述的动态单一分配。在这种情况下,由于绑定在运行时发生,因此也称为late binding。诸如smalltalk之类的语言允许动态多重调度,其中基于多个对象的标识或值在运行时选择运行时方法。

在C ++中,由于类型信息是已知的,因此我们实际上并没有后期绑定。因此,在C ++或Java上下文中,动态分发和后期绑定是相同的。我认为实际/完全后期绑定是在像python这样的语言中进行的,它是基于方法的查找,而不是基于类型的查找。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.