Code for Thought: Inheritance, name lookup and access control.

This thread was inspired by http://www.cplusplus.com/forum/general/1363/ where, frankly, we all got rather confused. There were a variety of issues that I think merit further discussion though. I'm pretty sure I was as much part of the problem in that thread as anyone, so here's my attempt at hacking through the undergrowth and making amends.

This question is modelled partially on Herb Sutter's marvelous series "Guru of the Week". (http://www.gotw.ca/gotw/index.htm) Obviously, I'm not a guru, but I thought his question style was a good one for prompting discussion. Again obviously, I think I know the answers, but I'm probably wrong. (Or at least my answers are incomplete) That's the point.

Anyway, onwards...

Consider the following code: (A complete, legal, and compilable program! Go on, try it)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>

using namespace std;

struct Base
{
// private:

   void f(char)
   {
      cout << "Base::f(char);\n";
   }

};

struct Derived : Base
{
   // using Base::f;

   void f(int)
   {
      cout << "Derived::f(int);\n";
   }
};

int main()
{
    Base b;
    Derived d;
    
    // Call f using an int as the argument
    b.f(10);
    d.f(10);
    
    // Call f using a char as the argument
    b.f('a');
    d.f('a');
}


1. What does it print and *why* ? (Also, why does C++ choose to do it this way?)
2. Uncomment line 18. Again, what does it print and why?
3. Uncomment line 7. What happens? Why is this the right thing to do?
4. What can we say about the order of name lookup, overload resolution, and the application of access control rules? Why is this important?

Don't answer straight away - try the code out, see if your compiler does what you expect.

(IMPORTANT NOTE: I believe there is more to this than meets the eye - particularly considering why C++ is they way it is and also the interactions between using directives and encapsulation)
Last edited on
And I guess you know the basic/hidden difference between a class and a structure in C++. A C++ structure is different from a class in only one way that a member in structure is public by default unlike private in a class.

When you remove the heading private/public/protected part, by default the declaration in following is considered a public in a struct while private in a class.

Keeping it in mind, and no polymorphysm (I mean, run time binding) is taken place in this program, there would obviously be no problem with access controls and all work as they are coded and appearted, meaning, it calls the functions in the given order, base, derived, base and derived.

Though I did not compile the code (I can not do it here) I can say that by looking at the code.

Good luck :)
A1 -
Base::f(char);
Derived::f(int);
Base::f(char);
Derived::f(int);

A2 -
Base::f(char);
Derived::f(int);
Base::f(char);
Base::f(char);

A3 -
compile error - Base::f() is private, so it can't be called directly and it can't be used in derived classes, such as Derived. This means that "using Base::f;" fails when uncommented, and subsequent calls to Base::f(char) fail, including those that try to call Derived::f(char), because Base::f(char) is inaccessible.

A4 -
The order of name lookup? Not sure if I understand, but it seems that the order is unimportant if there is a declaration that fits better. After that, access control is then used to determine whether the declaration can be used as the programmer attempts to use it. For example, b.f(10) would fail because it 'int' and 'char' are interchangeable when there isn't a suitable function for both. d.f('a') would also fail if Base::f() is private and "using Base::f;" is uncommented. The reason is because it would try to use Base::f(char) rather than Derived::f(int). This is because char matches char and int matches int in the Derived class. If the using line was commented out, then there would not be any issues with the Derived class. The base class would have issues still because Derived doesn't affect Base; Base affects Derived.

Not sure if that is what you were looking for, but I tried. :P
rpgfan3233:
>> The order of name lookup? Not sure if I understand,

Sorry, my bad. Not very clearly worded. What I'm getting at is that C++ performs overload resolution before it considers whether the chosen function is accessible. (Unlike Java)

Good start though.

What nobody has answered yet though, is why does C++ behave as it does? (Note: Java does it differently and works just fine!) Consider what would happen if the language rules were adjusted as follows:

1. Overload resolution still happens before considering accessibility. That is, the compiler can fail because a inaccessible function (i.e. private) is the best match for a given call.
2. Remove the need for the using keyword. That is, automatically consider functions with the same name in a base class during overload resolution.

Given these changes, would encapsulation still be possible in the general case?
Is it safe to write...
 
using Base::f;

... in the general case using the NORMAL rules?

Consider this code using the ADJUSTED language rules:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct A
{
private:
   void myPrivateFunction(char);
};

struct B
{
   void myPublicFunction()
   {
      // Intended to call myPrivateFunction(int) and perform some operation
      // on the char's value as an int.
      myPrivateFunction('a');
   }

private:
   void myPrivateFunction(int);
};
Last edited on
Topic archived. No new replies allowed.