views:

88

answers:

2

I was given the following code:

class FibHeapNode
{
     //...

     // These all have trivial implementation
     virtual void operator =(FibHeapNode& RHS);
     virtual int  operator ==(FibHeapNode& RHS);
     virtual int  operator <(FibHeapNode& RHS);

};

class Event : public FibHeapNode
{
     // These have nontrivial implementation
     virtual void operator=(FibHeapNode& RHS);
     virtual int operator==(FibHeapNode& RHS);
     virtual int operator<(FibHeapNode& RHS);

};

class FibHeap
{
     //...

     int  DecreaseKey(FibHeapNode *theNode, FibHeapNode& NewKey)
     {
          FibHeapNode *theParent;

          // Some code

          if (theParent != NULL && *theNode < *theParent)
          {
            //...
          }

          //...
          return 1;
     }
};

Much of FibHeap's implementation is similar: FibHeapNode pointers are dereferenced and then compared.

Why does this code work? (or is it buggy?) I would think that the virtuals here would have no effect: since *theNode and *theParent aren't pointer or reference types, no dynamic dispatch occurs and FibHeapNode::operator< gets called no matter what's written in Event.

+1  A: 

*theNode and *theParent are reference types. Infact FibHeapNode& ref is very equivalent to *theNode.

DeadMG
AndreyT
David Rodríguez - dribeas
`int* i = new int(0);*i = 1;std::cout << *i;` Strange, it outputs 1, almost as if *i was a reference to the int.
DeadMG
+4  A: 

You must be a bit confused about dynamic dispatch.

It is often said that "dynamic dispatch occurs only when you make a call through a pointer or through a reference". Formally speaking, this statement is totally bogus and misleading.

Dynamic dispatch in C++ happens always when you call a virtual function, with one and only one exception: dynamic dispatch is disabled when you use a fully qualified-name of the target function. For example:

some_pointer->SomeClass::some_function(); // fully-qualified name

In the above code the call will be dispatched statically, even if some_function is a virtual function. From the language point of view, there's no other ways to avoid the dynamic dispatch, i.e. in all other cases all calls to virtual functions are dispatched dynamically. What you use: a pointer, a reference, an immediate object - does not matter, the dispatch is still dynamic. Where you are calling the function from: from constructor/destructor or from somewhere else - does not matter, the dispatch is still dynamic. And I repeat: this is how things are from the point of view of the C++ language itself. This is how an "abstract C++ machine" works.

What happens in practice though, is that in many cases the dynamic dispatch can be replaced with static dispatch, because the compiler knows the dynamic type of the object in advance, at the compile-time and, consequently, knows the target of the dispatch. In such cases it makes more sense to call the target function directly, instead of going through the costlier dynamic dispatch mechanism. Yet, this is nothing else than just an optimization. Some people, unfortunately, mistake that optimization for language-mandated behavior, coming up with such meaningless statements as "dynamic dispatch occurs only when you make a call through a pointer or through a reference".

In your specific case the dispatch is dynamic. Since in your case the compiler does not know the dynamic types of the objects involved, it cannot optimize it into a static dispatch, which is why your code "works as intended".

P.S. Anticipating the possible questions about something I said above: Dynamic dispatch for calls made from constructors/destructors is limited by the current dynamic type of the object, which is why in straightforward cases it is possible (and easy) for the compiler to optimize them into a static dispatch. This is the reason for another popular urban legend that states that virtual calls from constructors/destructors are resolved statically. In reality, in general case they are resolved dynamically, as they should (again, observing the current dynamic type of the object).

AndreyT
You're saying that even if you call a method through an immediate object, the function address is resolved at runtime? This doesn't seem likely, given that this would be needlessly inefficient. What am I missing?
Oli Charlesworth
@Oli Charlesworth: No, what I'm saying is that from the point of view of C++ language even with an immediate object the call is resolved in accordance with the *dynamic* type of the object, i.e. the call is *dynamic*. However, with the immediate object the dynamic type is known at compile time, so, as I said above, most compilers will replace the dynamic call with static call. This is what you observe in practice. Static call in this case is not a C++ feature, but a mere compiler-specific optimization.
AndreyT
@AndreyT: at the risk of spiralling into semantics, isn't it somewhat meaningless to talk of the dynamic type of a early-bound variable? The semantics of `T a; a.foo();` are identical whether `foo` is a virtual function or not, so what does it mean to classify this as "dynamic dispatch"?
Oli Charlesworth
@Oli Charlesworth: C++ language makes no distinction between "early-bound" or "not early-bound" variables. Every expression in C++ has static type and dynamic type. And non-qualified calls to virtual functions are *always* resolved in accordance with the *dynamic* type of the object expression. There are no exceptions to this rule.
AndreyT
@Oli Charlesworth: As for "meaningless"... On the contrary, it would be meaningless to build up a complicated set of branching rules and additional definitions ("early-bound" anyone?) when there's absolutely no need for it. The above simple rule works perfectly in all cases. And the compiler optimizations in cases when the dynamic type is known in advance are very simple. What's not to like?
AndreyT
@AndreyT: I'd be genuinely interested to know where this notion comes from (i.e. the standard, etc.) given that in this case, the distinction is zero. I think it's a bit misleading to state that the quote in your answer is "totally bogus and incorrect and misleading nonsense"!
Oli Charlesworth
@Oli Charlesworth: The standard has no normative notion of "dispatch" at all (static or dynamic). The behavior of virtual functions is described in terms of static and dynamic type. The standard simply says that non-qualified calls to virtual functions are resolved in accordance with the dynamic type of the object expression. That's it. Nothing else. The important point is: all non-qualified calls to virtual functions are *equal*.
AndreyT
@AndreyT: I've found the reference you're talking about, and I take your point. However, I maintain that the original quote is not "bogus" or "misleading".
Oli Charlesworth
@Oli Charlesworth: I agree. But, I believe, in this specific case the source of OP's confusion is an attempt to apply the "popular" rule incorrectly, and the distinction between the "popular" rule and the formal specification matters.
AndreyT
An enlightening explanation. Thanks!