在C ++中dynamic分派和后期绑定有什么区别?
我最近阅读了维基百科上的Dynamic Dispatch,并且无法理解C ++中dynamic分派和后期绑定的区别。
当每一个机制被使用?
来自Wikipedia的确切引用:
dynamic分派不同于后期绑定(也称为dynamic绑定)。 在select操作的上下文中,绑定是指将名称与操作相关联的过程。 调度是指在决定名称所涉及的操作之后,为操作select一个实现。 通过dynamic调度,名称可以在编译时绑定到一个多态操作,但是直到运行时才会select实现(这是dynamic调度在C ++中的工作原理)。 但是,后期绑定确实意味着dynamic调度,因为在select名称引用的操作之前,您不能select要执行多态操作的实现。
一个相当体面的答案实际上被纳入到一个关于程序员迟到与早期绑定的问题上.stackexchange.com。
简而言之,后期绑定是指评估的对象 ,dynamic调度是指function方面。 在后期绑定中,variables的types是运行时的变体。 在dynamic调度中,正在执行的函数或子例程是variables。
在C ++中,我们并没有真正的延迟绑定,因为types是已知的 (不一定是inheritance层次结束,但至less是一个正式的基类或接口)。 但是我们有通过虚拟方法和多态的dynamic调度。
我可以为后期绑定提供的最佳示例是Visual Basic中的无types“对象”。 运行时环境为您做了所有迟到的重要工作。
Dim obj - initialize object then.. obj.DoSomething()
编译器实际上会为运行时引擎编写合适的执行上下文,以执行名为DoSomething
的方法的命名查询,如果使用正确匹配的参数发现,则实际执行底层调用。 实际上,对象的types是已知的(它inheritance自IDispatch
并支持GetIDsOfNames()
等)。 但是就语言而言,variables的types在编译时是完全未知的,并且不知道DoSomething
是否是实际上是 obj
的方法,直到运行时到达执行点为止。
我不会打扰C ++虚拟接口et'al,因为我相信你已经知道他们的样子。 我希望C ++语言显然不能这样做。 它是强types的。 它可以 (而且显然)通过多态虚拟方法function进行dynamic调度。
后期绑定是在运行时通过名称调用一个方法。 除了从DLL导入方法之外,你并没有在c ++中真正拥有这个function。
一个例子是: GetProcAddress ()
通过dynamic调度,编译器拥有足够的信息来调用方法的正确实现。 这通常通过创build一个虚拟表来完成。
链接本身解释了不同之处:
dynamic分派不同于后期绑定(也称为dynamic绑定)。 在select操作的上下文中,绑定是指将名称与操作相关联的过程。 调度是指在决定名称所涉及的操作之后,为操作select一个实现。
和
通过dynamic调度,名称可以在编译时绑定到一个多态操作,但是直到运行时才会select实现(这是dynamic调度在C ++中的工作原理)。 但是,后期绑定确实意味着dynamic调度,因为在select名称引用的操作之前,您不能select要执行多态操作的实现。
但是他们在C ++上大都是平等的,你可以通过虚拟函数和vtables来dynamic调度。
C ++使用早期绑定,并提供dynamic和静态调度。 调度的默认forms是静态的。 要获得dynamic调度,您必须将方法声明为虚拟。
在C ++中,两者是相同的。
在C ++中,有两种绑定方式:
- 静态绑定 – 这是在编译时完成的。
- dynamic绑定 – 这是在运行时完成的。
dynamic绑定,因为它在运行时完成,也被称为后期绑定,而静态绑定有时被称为早期绑定 。
使用dynamic绑定,C ++通过虚函数(或函数指针 )支持运行时多态,并且使用静态绑定,所有其他的函数调用都被parsing。
绑定是指将名称与操作相关联的过程。
这里主要的是函数参数,它们决定了在运行时调用哪个函数
调度是指在决定名称所涉及的操作之后,为操作select一个实现。
根据参数匹配调度控制
http://en.wikipedia.org/wiki/Dynamic_dispatch
希望这对你有所帮助
鉴于啰嗦的维基百科定义,我很想将dynamic分派分类为C ++的后期绑定
struct Base { virtual void foo(); // Dynamic dispatch according to Wikipedia definition void bar(); // Static dispatch according to Wikipedia definition };
对于维基百科而言,后期绑定似乎意味着指向C ++的成员指派
(this->*mptr)();
在哪里select什么是被调用的操作(而不仅仅是哪个实现)在运行时完成。
然而,在C ++文献中, late binding
通常用于维基百科所称的dynamic分派。
让我给你一个差异的例子,因为它们是不一样的。 是的,当你用超类引用一个对象的时候,dynamic调度可以让你select正确的方法,但是这个魔法对于这个类的层次结构是非常特殊的,你必须在基类中做一些声明才能使它工作(抽象方法填写的表,因为该表中的方法索引不能改变特定types)。 所以,你可以通过一个普通的Cat指针来调用Tabby和Lion和Tiger中的方法,甚至可以用Cat和Tigers和Tabbys来填充数组。 它知道这些方法在编译时(静态/早期绑定)在对象的vtable中引用了哪些索引 ,即使在运行时(dynamic分派)select了该方法 。
现在,让我们实现一个包含狮子,老虎和熊的数组! ((天啊!))。 假设我们没有一个名为Animal的基类,在C ++中,您将要做大量工作,因为编译器不会让您在没有公共基类的情况下执行任何dynamic分派。 vtables的索引需要匹配,而不能在未发布的类之间完成。 你需要一个足够大的虚拟表来保存系统中所有类的虚拟方法。 C ++程序员很less把这看作是一个限制,因为你已经被训练去思考一个关于类devise的方法。 我不是说好或坏。
在后期绑定的情况下,运行时没有共同的基类来处理这个问题。 通常有一个散列表系统用于在调度器中使用caching系统的类中find方法。 在C ++中,编译器知道所有types。 在后期语言中,对象本身知道它们的types(它不是无types的,对象本身也知道在大多数情况下他们是谁)。 这意味着如果我想(狮子,虎和熊),我可以有多种types的对象的数组。 您可以实现消息转发和原型devise(允许在不改变类的情况下改变每个对象的行为)以及各种其他方式,这些方式的灵活性更强,而且与不支持后期绑定的语言相比,代码开销更less。
有没有在Android中使用findViewById()? 你几乎总是最终将结果转换成正确的types,而且转换基本上是对编译器说谎,并放弃所有静态types检查的好处,这是为了使静态语言更好。 当然,你可以改为findTextViewById(),findEditTextById()和其他一百万个,这样你的返回types就可以匹配了,但是这会把多态性抛出窗口; 可以说是OOP的整个基础。 后期绑定的语言可能会让你简单地用一个ID来索引,并把它当作一个哈希表来处理,而不关心types被索引或返回。
这是另一个例子。 假设你有你的狮子类,它的默认行为是当你看到它的时候吃你。 在C ++中,如果你想拥有一个“训练过的”狮子,你需要创build一个新的子类。 原型可以让你简单地改变那个需要改变的Lion的一两个方法。 这是类和types不会改变。 C ++不能这样做。 这是很重要的,因为当你有一个从狮子inheritance的新的“非洲狮”时,你也可以训练它。 原型不改变类结构,因此可以扩展。 这通常是这些语言如何处理通常需要多重inheritance的问题,或者多重inheritance是您如何处理缺less原型的问题。
仅供参考,Objective-C与SmallTalk的消息传递相加,SmallTalk是原来的OOP,并且都具有上述所有function和更多function。 从微观层面来看,后期语言可能会稍微慢一些,但是通常可以让代码在macros观层次上更有效率地结构化,这一切都归结为偏好。
我想这意味着当你有两个类B时,Cinheritance了同一个父类A.所以,父指针(typesA)可以容纳每个子types。 编译器在某个时间内无法知道types在指针中保存了什么,因为在程序运行期间它可能会改变。
有一些特殊的function可以在一定的时间内确定某个对象的types。 像java中的instanceof
,或者通过if(typeid(b) == typeid(A))...
在c ++中。
这个问题可能会帮助你。
dynamic调度一般是指多次调度。
考虑下面的例子。 我希望它可以帮助你。
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*
。 这是由于缺乏dynamic的多派遣。
迟绑定是实现dynamic单派遣的机制之一。
在C ++中, dynamic dispatch
和late binding
都是一样的。 基本上,单个对象的值决定了在运行时调用的那段代码。 在像C ++和java这样的语言中,dynamic调度就是更具体的dynamic单个调度,就像上面提到的那样工作。 在这种情况下,由于绑定是在运行时发生的,因此也称为late binding
。 像smalltalk这样的语言允许dynamic多重调度,其中基于多个对象的身份或值在运行时select运行时方法。
在C ++中我们没有真正的后期绑定,因为types信息是已知的。 因此在C ++或者Java语境中,dynamic分派和后期绑定是一样的。 实际/完全后期绑定,我认为是像Python这样的语言,这是一种基于方法的查找,而不是基于types。
dynamic分派是在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
,因为该方法已被dynamic分派 。 C ++标准非常小心, 不要指定幕后发生的情况,但是每个在阳光下的编译器都会以相同的方式进行操作。 他们为每个多态types(称为虚拟表或虚表 )创build一个函数指针表 ,当调用虚方法时,从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分派到不同的function。 由于额外的间接步骤,调用虚拟函数比非虚拟调用要慢一点点。
另一方面,我对术语后期绑定的理解是函数指针在运行时按名称查找,从哈希表或类似的东西中查找。 这是事情在Python,JavaScript和(如果内存服务)Objective-C中完成的方式。 这使得可以在运行时向类中添加新的方法,这不能直接在C ++中完成。 这对于实现mixin这样的东西特别有用。 然而,缺点是运行时查询通常比使用C ++的虚拟调用要慢很多,编译器不能对新添加的方法执行任何编译时types检查。