views:

195

answers:

4

How it is determined whether the below call is bound at compile time or at runtime?

object.member_fn;//object is either base class or derived class object
p->member_fn;//p is either base class or derived class pointer

EDITED:

#include <iostream>
using namespace std;
class Base
{
       public:
          Base(){ cout<<"Constructor: Base"<<endl;}
          ~Base(){ cout<<"Destructor : Base"<<endl;}
};
class Derived: public Base
{
     //Doing a lot of jobs by extending the functionality
       public:
           Derived(){ cout<<"Constructor: Derived"<<endl;}
           ~Derived(){ cout<<"Destructor : Derived"<<endl;}
 };
void foo()
{
    Base & Var = Derived();
    Base*pVar = new Derived;
    delete pVar;
}
void main()
{
    foo();
        std::cin.get();
}


out put:
Constructor: Base
Constructor: Derived
Constructor: Base
Constructor: Derived
Destructor : Base // the Derived is not called,
                  // the PVal is of type Base* and the fn is not virtual 
                  //so compile time binding
Destructor : Derived
Destructor : Base
+2  A: 

In the first case, the type of object is known at compile time (assuming it's not a reference). So this will be a static binding.

In the second case, a dynamic binding will be used, provided that the function is virtual. Otherwise, a static binding.

Thomas
in case of reference it is going to behave like pointer?
Passionate programmer
A reference is a compile time notion and a pointer a runtime one -- they have different use : a reference is meant to allow pass-by-reference call semantics without having to resort to a pointer.
Hassan Syed
@Beginner: Yes, a reference is going to behave like a pointer, since that is what it is internally.
Tarydon
But it did not, When a Derived object is assigned to a reference of Base it just behaves like object rather than pointer, the class is not polymorphic
yesraaj
-1. Although this answer is technically correct, it's very misleading. The assumptions made for the two cases don't relate to the code. You could reverse them and draw the opposite conclusion for each.
Potatoswatter
@Potatoswatter I think his answer is correct, the code is added after sometime.
Passionate programmer
@Hassan Syed: you're wrong. A reference is nothing more than a const pointer -- not to be mistaken with a pointer to const ;).
Matthieu M.
@Matthieu: while under the hood they might be exactly the same, the slight differences at the higher level are what really matters. A reference cannot be NULL, so you know that it is not an optional value, for example. To me it is just like saying that a pointer and a pointer to a constant object are the same, yes they are: const-ness is only tested at compile time, from there on both are just an address into memory. The same goes with public/private members... all those examples have differences only at compile time, and yet you wouldn't say they are the same.
David Rodríguez - dribeas
@matthieu let me guess, the standard says that ? :P
Hassan Syed
@dribeas: unfortunately, a reference can be both NULL or left dangling, exactly like a pointer... but I agree that "normally", you should only obtain a reference from a valid object and not from dereferencing a pointer without testing for its nullity.
Matthieu M.
@Hassan: I don't know about the standard, the fact is that you're wrong because reference does not mean compile-time resolution. A virtual function is still resolved at run-time when invoked through a reference as it would through a pointer. I've personally always considered a reference as syntactic sugar for a pointer that should not be null and thus use it to document clearly my interfaces: if I expect a reference, I won't test for nullity, that's all.
Matthieu M.
For the skeptics, try this (evil) code: `Object`, seems artificial eh ? `std::vector<Object*> vec(1,0);` `assert(!vec.empty());` `Object` seems like code I have corrected not so far ago. Too bad they tested the vector for emptiness but did not extended the test to its content... too bad it provoked a crash too I guess :/
Matthieu M.
@Matthiew: no, you cannot. Dereferencing the NULL pointer is Undefined Behavior [intro.execution]/4, and it is quite explicit in the reference part: [dcl.ref]/4: 'in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the "object" obtained by dereferencing a null pointer.'
David Rodríguez - dribeas
@Matthiew (on the vector example): the code as it is written will not even compile (the two iterator templated constructor will be matched, instead of the (size,value) version). Then, if you modify it to trigger the appropriate constructor `std::vector<Object*> v(1u,static_cast<Object*>(0))` (only 1 of the two modifications is required not to match the template constructor) it falls back to the previous case: dereferencing the null pointer is UB. From there on, the system can do anything: crash an airplane, launch a global nuclear strike or end in 'Game Over' and call for professor Falken.
David Rodríguez - dribeas
+7  A: 

If the method is not virtual, both calls will be resolved at compile time. If the method is virtual then the first call in your question (obj.method()) will be resolved at compile time for an object, but at runtime for a reference. The second call (objp->method()) will be resolved at runtime. You can also force at compile time to call the non-derived version of a method.

struct base {
   void f();
   virtual void v();
};
struct derived : public base {
   void f();
   void v(); // intentionally left virtual out, it does not really matter
};
int main() {
   derived d;
   base & b = d;
   base * bp = &d;

   // compile time:
   d.f();   // derived::f
   d.v();   // derived::v
   b.f();   // base::f   -- non-virtual
   bp->f(); // base::f   -- non-virtual

   // runtime:
   b.v();   // derived::v
   bp->v(); // derived::v

   // compile time (user forced):
   b.base::v();   // base::v
   bp->base::v(); // base::v
}
David Rodríguez - dribeas
+1 examples are worth a thousand words.
Matthieu M.
+1, but I can't implicitly understand what does it mean by first call, that can be made first set of calls in code below, similarly second set of calls and third.
yesraaj
The things you say are resolved at runtime *might* actually be optimised by the compiler to be performed at compile time. If it has enough information and enough smarts to prove to itself that the object must be of a particular class, then a virtual call can be made non-virtual. They're "in principle" resolved at runtime.
Steve Jessop
With this simplistic example the compiler can actually perform those optimizations, but in most real world scenarios (where a reference or pointer is passed into a function or received as return value from another function) the compiler cannot actually do much there (unless again, the function is visible within the compilation unit --inlined...). At the end, no matter how many different scenarios can be optimized, the actual chances for a compiler to perform those optimizations in a real world application are small for the pure OO style and just a little less with metaprogramming.
David Rodríguez - dribeas
Agreed, it's just that since the question is "how is it determined that...", I think the answer should mention that it's sometimes up to the compiler. There's no reason I can think of for wanting to know whether a call to a virtual function is actually fixed up as if it were non-virtual, where you wouldn't also want to know that it's implementation-dependent behaviour.
Steve Jessop
+1  A: 

I think it needs to be made clear that the constructor and destructor calls in the first case are made against an unnamed derive object these are NOT the constructor or destructor of Var. Var is just a reference type that doesn't require any special handling. As the compiler knows the type of this unnamed Derived object it correctly statically binds these to the derived class.

Similarly the constructor is statically bound in the second case because type following the new is derived. However when you call delete on the pointer of type base the compiler calls Base's destructor (again with static binding).

If you declared the base's destructor virtual then this final binding - that of which destructor to call when the delete happens would be dynamic and you would get identical output to that of the first case.

Elemental
+1  A: 

Dynamic binding is used only in case of pointer/reference and function called is virtual,

object.member_fn;//if object is not reference so static binding
object.member_fn;//if object is reference and member_fn is virtual dynamic binding
p->member_fn;//p is pointer and member_fn is virtual dynamic binding

Though there is one case where the reference bounds itself to a temporary will call the correct destructor.

#include <iostream>
using namespace std;
//virtual behavior with out the type being polymorphic
class Base
{
public: Base(){}
    ~Base(){}
};
class Derived:public Base
{
public:Derived(){}
       ~Derived(){
           std::cout<<"Destructor is not virtual, but it is called";
       }
};
void foo(){
    Base & bound_to_temporary = Derived();
}
int main(){
    foo();
    cin.get(); 
}

Output: Destructor is not virtual, but it is called.

yesraaj
... and if the compiler can't predict the dynamic type. E.g. after `std::string const`, the compiler can reliably predict the dynamic type of `foo`, even though it is a reference.
MSalters