12

In the following example, I'm trying to hide using Employee::showEveryDept from the last child class Designer by making it private in class Elayer -

#include <iostream>

class Employee {
private:
    char name[5] = "abcd";
    void allDept() { std::cout << "Woo"; }

public:
    void tellName() { std::cout << name << "\n"; }
    virtual void showEveryDept()
    {
        std::cout << "Employee can see every dept\n";
        allDept();
    }
};

class ELayer : public Employee {
private:
    using Employee::showEveryDept;

protected:
    ELayer() {}

public:
    using Employee::tellName;
};

class Designer : public ELayer {
private:
    char color = 'r';

public:
    void showOwnDept() { std::cout << "\nDesigner can see own dept\n"; }
};

int main()
{
    Employee* E = new Designer;
    E->showEveryDept(); // should not work

    Designer* D = dynamic_cast<Designer*>(E);
    D->showOwnDept();
}

But it is still compiling and the output is -

Employee can see every dept
Woo
Designer can see own dept

But I've explicitly made it private, see - private: using Employee::showEveryDept;

What am I doing wrong here?

M.M
  • 138,810
  • 21
  • 208
  • 365
hg_git
  • 2,884
  • 6
  • 24
  • 43

5 Answers5

14

You are thinking about it the wrong way.

C++ has the concept of Name Lookup, it's a well built concept that doesn't often come to our minds, but this happens everywhere a name is used. By doing:

int main()
{
    Employee* E = new Designer;
    E->showEveryDept(); // should not work

    Designer* D = dynamic_cast<Designer*>(E);
    D->showOwnDept();
}

The line, E->showEveryDept() does an unqualified name lookup for a member (member function in this case) belonging to the class of E. Since it's an accessible name, the program is legal.


We, also know Designer was derived from ELayer, where showEveryDept() is declared private as you did here:

class ELayer : public Employee {
private:
    using Employee::showEveryDept;

protected:
    ELayer() {}

public:
    using Employee::tellName;
};

But what you simply did was to explicitly introduce the name showEveryDept() from Employee class into Elayer; introduced into a private access. Meaning, we cannot directly access that name (or call that function) associated with ELayer outside the class member/static functions.

ELayer* e = new Designer();
e->showEveryDept();    //Error, the name showEveryDept is private within ELayer

However, since showEveryDept() has a public access within the base class of Elayer, Employer; it can still be accessed using qualified name lookup

ELayer* e = new Designer();
e->Employer::showEveryDept();    //Ok, qualified name lookup, showEveryDept is public

The names from Elayer which are accessible in Designer will be dictated by its access specification. As you can see, the name, showEveryDept() is private, so Designer cannot even use such name.

For your current class hierarchy and definitions, it means that, given:

Employee* E = new Designer();
ELayer*   L = new Designer();
Designer* D = new Designer();

-- For unqualified lookup:

  • This works

    E->showEveryDept();                // works!
    
  • This fails:

    L->showEveryDept();                // fails; its private in Elayer
    
  • This also fails:

    D->showEveryDept();                // fails; its inaccessible in Designer
    

-- For qualified lookup, in this case, requesting to call such function from its base class:

  • This fails:

    D->Elayer::showEveryDept();        // fails! its private in Elayer
    
  • This works:

    D->Employee::showEveryDept();      // works! its accessible in Employee
    
  • This also works:

    L->Employee::showEveryDept();      // works! its accessible in Employee
    

Note that: introducing the name of any member function, B::func(for example) from a base class B, into a derived class D, does not override B::func, it simply makes B::func visible to overload resolution within the context of the derived class. See the answers to this question and this

Community
  • 1
  • 1
WhiZTiM
  • 21,207
  • 4
  • 43
  • 68
  • My aim is to let it get called directly by `Employee` class objects, but fail if it is being called by anyone who derives from `ELayer` – hg_git Sep 17 '16 at 07:48
  • This is a very good answer, and the only reason I accepted other because that was more clear to me. Still you answer did help me a lot :) – hg_git Sep 26 '16 at 13:17
5

The statement

E->showEveryDept();

accesses showEveryDept in the type known at compile time for *E. Which is Employee, where this member is accessible.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • 1
    would you mind correcting me? `Employee* E = new Designer;` defines a pointer of type `Employee` but points to an object of type `Designer`, right? So `E->showEveryDept()` is actually calling `showEveryDept()` method on `Designer` object, or no? Because then it is well hidden! – hg_git Sep 17 '16 at 06:08
  • Yes, the virtual call resolves down to the most derived class. But the rules are as they are because in the general case that most derived class can't be known at compile time, even with global analysis. E.g. it can depend on user input. – Cheers and hth. - Alf Sep 17 '16 at 06:16
  • 3
    Not sure if you maybe think `private` virtual member functions can't be called virtually. They can. Many people recommend making virtual member functions `private`, although, for some unexplained reason (I can't recall discussing this with Marshall) it's not the recommendation of the old FAQ, and therefore probably not of the ISO CPP FAQ either. – Cheers and hth. - Alf Sep 17 '16 at 06:19
  • 2
    @Cheersandhth.-Alf - https://isocpp.org/wiki/faq/strange-inheritance#private-virtuals 3rd para. – Abhinav Gauniyal Sep 17 '16 at 06:39
  • 2
    @AbhinavGauniyal: Thanks! Summary: the ISO CPP FAQ does recommend private virtuals, and explains why the old FAQ did not (risk of novice confusion). – Cheers and hth. - Alf Sep 17 '16 at 06:44
  • It might be good to mention the difference between *static type* and *dynamic type* of `*E`, since that is apparently where OP's confusion lies – M.M Sep 26 '16 at 00:45
2

Names of class members have the following properties:

  • The name - an unqualified identifier.
  • The declarative region - which class the name was declared in.
  • The access - rights of the name within that region.

This applies to the names themselves -- not to any variable or function that a name refers to. It is possible to have the same function or variable named by the same name but in a different declarative region.

When a class is inherited, the derived class's declarative region includes all names from the base class; but the access may be changed based on the type of inheritance: although it's only possible to declare a member as public, protected, or private, after inheritance you can end up with a member having no access.

Here is a table of accessability of names and regions in your code:

Name accessibility

Note how tellName is public in all three classes, despite the fact that it was not redeclared in Designer. Accordingly, the ELayer's using Employee::tellName; is redundant because tellName would have been public in ELayer anyway.

The effect of ELayer's using Employee::showEveryDept; is that showEveryDept's access within ELayer is private.


Name Lookup is the process of resolving which name-region combination is found by a call to a name. The context of this call includes:

  • The call site, i.e. the scope in which the name was used
  • Any explicitly-listed scope in the call (e.g. Foo::name)
  • The expression denoting object whose member is being accessed (e.g. (*E))

Access control also takes into account:

  • The relationship between the calling context and the declarative region in which the name was found.

For example, looking up showEveryDept in the context of ELayer will find the combination ELayer::showEveryDept with access private.

But looking up the same name in the context of Employee will find the combination Employee::showEveryDept which has access public.

This behaviour is the same whether or not those two combinations refer to the same function.

Without reproducing the full list of rules about how the calling context translates to which declarative regions are searched, the usage:

`E->showEveryDept`

looks up the name in the region of the static type of *E, which is Employee. It does not use the dynamic type, because name lookup is resolved at compile-time. There are no run-time access errors -- access is a compile-time property.

The final step of the access check is to compare public and Employee with the call site, which is main(). The rule is that public grants access to all call sites, so the access check passes.


virtual-ness does not depend on the properties of names, nor the scope in which the name is being looked up. Unlike access, being virtual is a property of the function, not of any name-region combinations.

When virtual dispatch is active, calling a function will redirect the call to the final overrider of that function.

It's important to keep thinking of this in terms of function implementations - not of names for the functions. Virtual dispatch and Access Control are two completely separate operations.

Virtual dispatch is active only when a virtual function is called by an unqualified-id, which means by naming the function without Bla:: on the front.

So, in your code, E->showEveryDept does activate virtual dispatch. The access check passes as described above, and then virtual dispatch invokes the final overrider, which happens to be the body defined in Employee in this example.

In your actual example, virtual is moot since the function is not overridden. But even if you had overridden showEveryDept as a private function in ELayer (instead of the using declaration), it would still call that function body.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • you must be a teacher by profession, knew exactly what I was missing to understand this problem :D – hg_git Sep 26 '16 at 13:13
  • 1
    @hg_git heh, thanks for the award... I'm not sure what you were missing but I tried to hit all the bases – M.M Sep 26 '16 at 13:15
0

In your main() function, be explicit about your type and call it like this -

int main()
{
    Employee* E = new Designer;
    E->showEveryDept(); // will work

    Designer* D = dynamic_cast<Designer*>(E);
    D->showOwnDept();
    D->showEveryDept();   // <-- Not legal now
}

It will produce the error -

prog.cc: In function 'int main()':
prog.cc:28:22: error: 'virtual void Employee::showEveryDept()' is inaccessible within this context
     D->showEveryDept();
                      ^
prog.cc:8:26: note: declared here
             virtual void showEveryDept(){std::cout<< "Employee can see every dept\n";
Abhinav Gauniyal
  • 7,034
  • 7
  • 50
  • 93
0

What am I doing wrong here?

You are not doing anything wrong.*

Is the expected result that is wrong:

The access specifier of the virtual function is checked on the type Employee and not on Designer as you might expect => link

(*) Except the fact that changing the access rule to a virtual method over the hierarchies is bad design to my mind => check this .

Community
  • 1
  • 1
Emilio
  • 21
  • 2