3

I learned something new about C++ the other day; the following does not work:

class ParentClass {
public: 
    void someFunction() { printf("ParentClass::someFunction()"); }
};

class ChildClass : public ParentClass {    
public:
    void someFunction(int a) { printf("ChildClass::someFunction(int)"); }
};

int main() {
    ChildClass childClass;
    // This call is a compiler error.  
    // I would expect it to call ParentClass::someFunction()
    childClass.someFunction();
}

However, doing exactly the same thing in Java (among other languages) works just as I would expect:

public class ParentClass {
    public void someFunction() { System.out.println("ParentClass"); }
}

public class ChildClass extends ParentClass {
    public void someFunction(int a) { System.out.println("ChildClass"); }
}

public class Main {
    public static void main(String[] args) {
        ChildClass childClass = new ChildClass();
        // The following prints "ParentClass"
        childClass.someFunction();
    }
}

So what gives in C++? Why does this hide the name instead of overloading it?

Jim
  • 833
  • 6
  • 13
  • `However, doing exactly the same thing in Java` This is why you shouldn't use Java (or another language that looks like C++) as a model when writing C++ code. – PaulMcKenzie Feb 18 '15 at 15:08
  • Hi Paul. I just wanted to make sure I understand how C++ came to the decision of which method to call so that I can learn from it and design my code differently next time. My mental model of how it worked followed how Java does it so I provided it as an example. – Jim Feb 18 '15 at 15:53

3 Answers3

3

If you're asking what the rules are, then name lookup stops as soon as it finds one or more overloads within one scope, and doesn't look at any wider scopes. So, in your case, the search for someFunction starts in the scope of ChildClass, finds a match, and stops.

Only the name is considered, not its usage (e.g. number of arguments in a function call), accessibility, or anything else. If none of the overloads are usable, the search still doesn't continue to other scopes, and the program is ill-formed.

If you're asking why the rules are like that, consider the case where, initially, there's just one function:

struct Base {};
struct Derived : Base {void f(int);}

and someone calls it with a type that doesn't quite match

Derived d;
d.f(42.0);  // OK: double converts to int

Now suppose someone, who knows nothing about Derived, decides that Base could do with another function:

struct Base {
    void f(double);  // Completely unrelated to D::f
};

Under the C++ rules, that function will be ignored by the code using D::f, which will continue to work as before. If the new function were considered as an overload, it would be a better match, and the code using D::f would suddenly change behaviour, potentially leading to much head-scratching and lengthy debugging sessions.

If you want to include all the base-class functions in the scope of the derived class to be considered as overloads, then a using-declaration will do that. In your case:

using ParentClass::someFunction;

Alternatively, to avoid the situation described above at the cost of some tedious verbiage, you could write forwarding function(s) for the specific overload(s) you want:

void someFunction() {ParentClass::someFunction();}
Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • Thanks Mike (And everyone else who answered). – Jim Feb 18 '15 at 15:38
  • That makes sense but I guess I disagree with the design decision then. I would expect it to call a matching signature (even if it's in a base class) before converting arguments to different types to match different signatures. – Jim Feb 18 '15 at 15:49
  • @Jim: I've explained the reasoning behind the C++ choice: it prevents additions to classes you're not (directly) using - or the surrounding namespace(s) - from breaking your code in confusing ways. A simple using-declaration will give you the behaviour you think you want. – Mike Seymour Feb 18 '15 at 15:53
1

In C++, name hiding can take place when one function in base class has the same name as one function in derived class. The reason is phases of the function call process.

In C++, phases of the function call process are as following

  • Name lookup
  • Overload resolution
  • Access control

Name lookup stops looking for other names as soon as it finds a name in derived class ChildClass. Therefore, ChildClass::someFunction() hides any function with name someFunction in ParentClass.

After the name lookup process, overload resolution fails since there is no someFunction() in ChildClass.

Alper
  • 12,860
  • 2
  • 31
  • 41
1

The core difference is that in C++ the method signature is essentially just the method name whereas in Java it is the method name and its parameters.

In your case, by using the same method name you are overriding the parent method so the parent method taking no parameters is not available any more. In Java you must override with a method that both has the same name and has the same parameters to override a method so in your case they are both still available.

There is a completely different debate about whether the return type should also be included - let's not go there.

OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • C++ method signatures also include the arguments because you can overload methods within the same scope and that works fine (This is not true in regular C). Mike Seymour's (And Alper's) explanation makes a lot of sense. – Jim Feb 18 '15 at 15:50