Hide, overload or overwrite in C++

1.6k Views Asked by At

Then I have a question again. something like this:

#include <iostream>
using namespace std;

class Base
{
public:
    void foo()
    {
        cout<<"Base."<<endl;
    }
};

class Derive:public Base
{
public:
    void foo()
    {
        cout<<"Derive."<<endl;
    }
};

int main()
{
    Derive d;
    Base *pb=&d;    //attention here
    pb->foo();      //ateention here
    system("pause");
    return 0;
}

And the output is "Base.". Then the function rules are't work, I am confused about this, could you help me? Thanks.

5

There are 5 best solutions below

1
On BEST ANSWER

Since foo is not virtual, the function called is based on the static type (i.e., the type the pointer is declared to point at) rather than the dynamic type (the type of object to which the pointer currently refers).

There are also some trickier cases to consider. One point (on which some of the other answers are actually somewhat misleading) is that it's not really just the function name that matters, but the entire function signature. For example:

#include <iostream>

struct base { 
    virtual void foo() { 
        std::cout << "base::foo";
    }
};

struct derived : base { 
    virtual void foo() const { 
        std::cout << "derived::foo";
    }
};

int main(){ 
    base *b = new derived;

    b->foo();
}

Here foo is qualified as virtual in both the base and (seemingly redundantly the) derived classes, but the call b->foo() still prints out base::foo.

The const added to the signature of derived::foo means it no longer matches the signature of base::foo, so instead of overriding the virtual function, we still end up with two separate functions with the same name, so derived::foo hides base::foo, but doesn't override it. Despite the virtual qualification, we get static binding, so b->foo(); invokes the base function rather than the derived, even though b points to an object of the derived type.

As Tony D pointed out in a comment, C++11 added a new wrinkle to the language to help ensure against this happening. When you want to override a base class function, you can add the identifier override to the function in the derived class:

struct derived : base {
     virtual void foo() const override {
         std::cout << "derived::foo";
     }
};

With this, if there's a difference in function signature (as in the cases shown here), the compiler will produce an error message alerting you to the fact that derived::foo is marked as override, but doesn't actually override a function from the base class. This was, however, added in C++11, so if you're using an older compiler this feature may not be implemented (though thankfully, compilers that don't implement it are quickly fading into oblivion).

Correcting the signature in the base class to:

virtual void foo() const // ...

...will let the code compile, and produce the correct results.

0
On

Judging by the title of your question, I take it that you don't fully understand when functions get hidden, overloaded, and overwritten.

Sample code 1:

struct Base
{
   void foo()
   {
   }
};

struct Derive: public Base
{
   void foo()
   {
   }
};

int main()
{
   Derive d;
   Base *pb=&d;
   d.foo();    // Resolves to Derived::foo()
   pb->foo();  // Resolves to Base::foo()
   return 0;
}

Why does d.foo() call Derived::foo() and pb->foo() call Base::foo()?

The answer to that question lies in the steps the compiler takes to resolve those function bindings.

Given an object of type T and a function name f, the compiler looks up the functions named f in T. If it finds only one function named f, the search for functions stops there. If it finds more than one function, it attempts overload resolution from the set of functions found in T.

If it does not find any functions named f in T and T has a base class, it tries the above logic in T's base class. If T does not have any base classes, the compiler reports an error.

Coming to the objects of the example code...

When processing the function call d.foo(), the compiler looks for foo in Derived. It finds one match there and it stops. Since the Derived::foo() is not a virtual function, the binding is done at compile time. At run time, Derived::foo() is called.

When processing the function call pb->foo(), the compiler looks for foo in Base. It finds one match there and it stops. Since the Base::foo() is not a virtual function, the binding is done at compile time. At run time, Base::foo() is called.

Sample code 2:

struct Base
{
   void foo(int i)
   {
   }
};

struct Derive: public Base
{
   void foo()
   {
   }
};

int main()
{
   Derive d;
   Base *pb=&d;
   d.foo();      // Resolves to Derived::foo()
   d.foo(10);    // Compiler error.

   pb->foo(10);  // Resolves to Base::foo(int)
   pb->foo();    // Compiler error.
   return 0;
}

Why does the compiler produces the errors here?

When processing the function call d.foo(10), the compiler looks for foo in Derived. It finds one match there and it stops. It tries to use that function but the function's signature does not match the calling code. Hence, it is a compiler error.

When processing the function call pb->foo(), the compiler looks for foo in Base. It finds one match there and it stops. It tries to use that function but the function's signature does not match the calling code. Hence, it is a compiler error.

Once the compiler finds a foo in Derived it does not go searching for a matching foo in Base.

In this case, you can think of Derived::foo to be completely hiding Base::foo.

Sample code 3:

struct Base
{
   void foo()
   {
   }
};

struct Derive: public Base
{
   void foo()
   {
   }
   void foo(int )
   {
   }
};

int main()
{
   Derive d;
   d.foo();      // Resolves to Derived::foo()
   d.foo(10);    // Resolves to Derived::foo(int)

   Base *pb=&d;
   pb->foo();    // Resolves to Base::foo()
   pb->foo(10);  // Compiler error.

   return 0;
}

When processing the function calls d.foo() and d.foo(10), the compiler looks for foo in Derived. It finds couple of matches there and it stops. Then it tries overload resolution. It is able to find a match for both versions. Since neither of the Derived::foo()s is a virtual function, the binding is done at compile time.

When processing the function calls bp->foo() and bp->foo(10), the compiler looks for foo in Base. It finds couple of matches there and it stops. Then it tries overload resolution. It is able to find a match for the first versions but not the second version. It generates an error for the second call.

Here Derived::foo not only hide Base::foo but also there are two overloaded versions of Derived::foo.

Sample code 4:

struct Base
{
   virtual void foo()
   {
   }
   void foo(int)
   {
   }
};

struct Derive: public Base
{
   void foo()
   {
   }
   void foo(int )
   {
   }
};

int main()
{
   Derive d;
   d.foo();      // Resolves to Derived::foo()
                 // But Derived:foo() gets called at run time.

   d.foo(10);    // Resolves to Derived::foo(int)
                 // But Derived:foo(int) gets called at run time.

   Base *pb=&d;
   pb->foo();    // Resolves to Base::foo()
                 // But Derived:foo() gets called at run time due to
                 // function overwritting.

   pb->foo(10);  // Resolves to Base::foo(10)
                 // Base:foo(int) gets called at run time.

   return 0;
}

This includes function hiding, function overloading, and function overwritting.

Derived::foo hides Base::foo.
Derived::foo() and Derived::foo(int) are overloaded.
Base::foo() and Base::foo(int) are overloaded.
Base::foo() is overwritten by Derived::foo().

I hope that clears up some of your doubts.

1
On

There's no overloading or overwriting that's going on in this code. Base::foo is being called rather than Derive::foo because there hasn't been any specification by the programmer to use dynamic binding for the name foo. If the virtual-specifier isn't provided, the compiler finds the function based on the static type of the object on which it is called, rather than the type it may point to refer to. This is known as static binding, and it's done at compile-time.

If the virtual-specifer is used, the name of the function is looked up at runtime and called based on the runtime type of the object.


As an aside, your base class needs a virtual destructor for the same reason written above. If you have a Base class pointer that points to Derive deleting that pointer will only call the base class destructor rather than both the base and derived.

4
On

Function Base::foo is non-virtual. It is called because the pointer to the base class is used.

If you change the code like this:

class Base
{
public:
    virtual void foo() // add virtual
    {
        cout<<"Base."<<endl;
    }
};

the output should be "Derived".

3
On

You see this behavior because foo() is not declared as virtual in Base. In C++, member functions are non-virtual by default. You must explicitly declare a function as virtual in order to take advantage of dynamic dispatching and polymorphism.