C ++中动态调度和后期绑定有什么区别?

时间:2013-11-25 08:15:53

标签: c++ oop

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

何时使用每种机制?

维基百科的确切引用:

  

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

11 个答案:

答案 0 :(得分:59)

对此的一个相当不错的答案实际上被纳入关于programmers.stackexchange.com的晚期与早期约束的问题。

简而言之,后期绑定是指eval的对象 - ,动态调度是指功能方面。在后期绑定中,变量的类型是运行时的变体。在动态分派中,正在执行的函数或子例程是变体。

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

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

Dim obj

- initialize object then..
obj.DoSomething()

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

我不会打扰转储C ++虚拟接口et'al,因为我相信你已经知道它们的样子了。我希望很明显,C ++语言根本无法做到这一点。它是强类型的。 可以(并且显然)通过多态虚方法功能进行动态调度。

答案 1 :(得分:8)

后期绑定在运行时期间按名称调用方法。  除了从DLL导入方法外,你在c ++中并没有这个 例如: GetProcAddress ()

使用动态分派,编译器有足够的信息来调用方法的正确实现。这通常是通过创建 virtual table

来完成的

答案 2 :(得分:8)

link本身解释了差异:

  

动态调度与后期绑定(也称为动态绑定)不同。在选择操作的上下文中,绑定是指将名称与操作相关联的过程。调度是指在确定名称引用的操作后选择操作的实现。

  

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

但它们在C ++中大致相同,你可以通过虚函数和vtable进行动态调度。

  

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

答案 3 :(得分:5)

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

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

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

动态绑定,因为它在运行时完成,也称为后期绑定,静态绑定有时称为早期绑定

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

答案 4 :(得分:5)

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

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

调度是指在确定名称所指的操作后选择操作的实现。

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

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

希望这能帮到你

答案 5 :(得分:3)

让我举一个例子,因为它们不一样。是的,动态调度允许您在超类引用对象时选择正确的方法,但该魔法非常特定于该类层次结构,并且您必须在基类中执行一些声明才能使其工作(抽象方法)填写vtable,因为表中方法的索引不能在特定类型之间更改)。因此,您可以通过一般的Cat指针调用Tabby和Lion和Tiger中的方法,甚至可以使用充满Lions,Tigers和Tabbys的Cats数组。它知道这些方法在编译时在对象的vtable中引用的索引(静态/早期绑定),即使在运行时选择了方法(动态调度) )。

现在,让我们实现一个包含Lions,Tigers和Bears的数组! ((天啊!))。假设我们没有一个名为Animal的基类,在C ++中,你将要做很多工作,因为编译器不会让你在没有公共基类的情况下进行任何动态调度。 vtable的索引需要匹配,而未完成的类之间无法完成。你需要一个足够大的vtable来保存系统中所有类的虚方法。 C ++程序员很少将此视为一种限制,因为您已经接受过训练,可以对类设计进行某种思考。我不是说它好或坏。

使用后期绑定,运行时可以在没有公共基类的情况下处理此问题。通常有一个哈希表系统用于在类中找到具有调度程序中使用的缓存系统的方法。在C ++中,编译器知道所有类型。在后期绑定语言中,对象本身知道它们的类型(它不是无类型的,对象本身确切地知道它们在大多数情况下是谁)。这意味着如果我需要(Lions and Tigers和Bears),我可以拥有多种类型对象的数组。并且您可以实现消息转发和原型设计(允许每个对象更改行为而不更改类)以及各种其他方式,这些方式比不支持后期绑定的语言更灵活,并且导致更少的代码开销

在Android中编程并使用findViewById()吗?您几乎总是最终将结果转换为获得正确的类型,并且转换基本上是对编译器撒谎并放弃所有静态类型检查的好处,这应该使静态语言更优越。当然,您可以使用findTextViewById(),findEditTextById()和其他一百万个来使您的返回类型匹配,但这会将多态性抛出窗口;可以说是OOP的整个基础。后期绑定语言可能会让您简单地按ID索引,并将其视为哈希表,而不关心索引或返回的类型。

这是另一个例子。假设您拥有Lion类,其默认行为是在您看到它时吃掉它。在C ++中,如果你想拥有一个“训练有素”的狮子,你需要创建一个新的子类。原型设计可以让您简单地更改需要更改的特定Lion的一种或两种方法。它的类和类型不会改变。 C ++不能那样做。这很重要,因为当你拥有一个继承自Lion的新“AfricanSpottedLion”时,你也可以训练它。原型设计不会改变类结构,因此可以扩展。这通常是这些语言如何处理通常需要多重继承的问题,或者可能是多重继承是如何处理缺乏原型设计的。

仅供参考,Objective-C是C,其中添加了SmallTalk的消息传递,而SmallTalk是原始的OOP,并且两者都具有上述所有功能的后期绑定。从微观层面来看,后期绑定语言可能稍微慢一点,但通常可以允许代码以在宏观层面上更有效的方式构建,并且这一切都归结为偏好。

答案 6 :(得分: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通常用于维基百科所谓的动态调度。

答案 7 :(得分:1)

动态调度是在C ++中使用virtual关键字时发生的事情。例如:

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将调用分派给不同的函数。由于额外的间接步骤,对虚拟函数的调用比非虚拟调用慢一点。


另一方面,我对术语后期绑定的理解是,在运行时,通过哈希表或类似的东西, name 查找函数指针。这是用Python,JavaScript和(如果内存服务)Objective-C完成的事情。这使得可以在运行时的类中添加新方法,这不能直接在C ++中完成。这对于实现mixins这样的东西特别有用。但是,缺点是运行时查找通常比C ++中的虚拟调用慢得多,并且编译器无法对新添加的方法执行任何编译时类型检查。

答案 8 :(得分:0)

我认为意思是当你有两个类B时,C继承同一个父类A.所以,父的指针(类型A)可以保存每个儿子类型。编译器无法在一定时间内知道指针中的类型,因为它可以在程序运行期间更改。

有特殊功能可以确定某个特定对象在特定时间内的类型。比如java中的instanceof或c ++中的if(typeid(b) == typeid(A))...

答案 9 :(得分:0)

question可能会对您有所帮助。

动态调度通常指多次调度。

考虑下面的例子。我希望它可以帮助你。

    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*。这是由于缺乏动态多次调度。

后期绑定是实现动态单一调度的机制之一。

答案 10 :(得分:0)

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

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