h9uest wrote: |
---|
But how do I access the hidden function "void print (void) {...}" from OUTSIDE the derived class? |
I probably didn't make myself clear, but this is the purpose of the scope resolution operator.
When I define "void print (int i) {...}" for class Derived, effectively it SHOULD be a case of "overload": i.e. we should be able to call both "son.print ()" & "son.print (2)" in main. |
Overloading is what happens when you have functions with the same name but different signatures in the same scope. This is not your case. In your case, what happens resembles more function declaration in block scope hiding one from the global scope.
Let me illustrate the analogy. If you have these two declarations in file scope:
1 2
|
void print(void);
void print(int);
|
, then you have overloaded the function print.
Now, imagine that the situation is different. Imagine that you have:
1 2 3 4 5
|
void print(void);
void g() {
void print(int);
print(); //ERROR
}
|
The
definition declaration in block scope is not overload of the global one, but hides it instead. Hiding does not require the signatures to match, only the names. In other words, introducing local object hides all global objects with the same name, even if they are functions whose signatures are different. So, what do we do in cases like this, and in any case of name hiding in general? We use fully qualified names that are given with the scope resolution operator. Like this:
1 2 3 4 5
|
void print(void);
void g() {
void print(int);
::print(); //OK
}
|
How does this relate to your question, you ask. Well, the situation is very similar. The derived class indeed inherits the function from the base class, but what also happens is that the derived class introduces inner scope where all its declarations (from the body of its class definition) live. Anything declared in the scope of the derived class hides anything declared in the scope of the base class that has the same name. The scope of the derived class is like the scope of the function g in my previous example, and the scope of the base class is analogous to the global scope. Now, trying to access
void print(void) directly, like this:
1 2
|
Derived d;
d.print(); //ERROR
|
would produce an error, because this print function has been hidden. So, what do we do, you ask. The same as before, we use the scope resolution operator, but in slightly different form. Like this:
1 2
|
Derived d;
d.Base::print(); //OK
|
In summary:
- all members from the base class are inherited in the derived class
- some of those members are hidden in the derived class, but can still be used by accessing them with the scope resolution operator
- the hidden members can be imported in the scope of the derived class (see my previous post)
- even if a member is not hidden it may be private in the base class and therefore inaccessible; not only to the outside world, but also to the derived class, although it can be overloaded nonetheless
The key terms are visibility and accessibility.
So, you have LSP (Liskov substitution principle), but not on the syntactical level. That is, a template for example may work ok with the base class, but not with the derived class, because of visibility and accessibility issues. But in principle, all functions from the base class apply to the derived class, just as long as you can locate and access them.
I understand your quarrel with the language. You are probably asking yourself the question - with all this language rules, what is the conceptual meaning of two types being in inheritance relationship. But, I ask, what is the meaning of inheritance in general? I mean, equivalence in terms of substitution sounds very solid until you try to dissect it in fine detail and although it is useful idea, it also relatively vague and informal.
Best Regards