Public virtual function derived private in C++
Asked Answered
P

3

21

I was trying to figure out what happens when a derived class declares a virtual function as private. The following is the program that I wrote

#include <iostream>
using namespace std;
class A
{
    public:
        virtual void func() {
        cout<<"A::func called"<<endl;
    }
    private:
};
class B:public A
{
    public:
    B()
    {
        cout<<"B constructor called"<<endl;
    }
    private:
    void func() {
        cout<<"B::func called"<<endl;
    }
};
int main()
{
    A *a = new B();
    a->func();
    return 0;
}

Surprisingly (for me) the output was:

B constructor called
B::func called

Isn't this violating the private access set for that function. Is this the expected behavior? Is this is a standard workaround or loophole? Are access levels bypassed when resolving function calls through the VTABLE?

Any insight in to this behavior would be greatly helpful.

Further it was mentioned that a privately overriding a virtual member would prevent further classes from inheriting it. Even this is having problems. Modifying the above program to include:

class C: public B
{
    public:
    void func() {
        cout<<"C::func called"<<endl;
    }
};

and the main test program to:

int main()
{
    A *a = new C();
    a->func();
    return 0;
}

output is:

C::func called
Pickar answered 30/6, 2009 at 4:28 Comment(2)
I just wanted to point out that one cannot do this in java. When a method is overridden, it must have an access level that is equal to or more public than the method that is being overridden. The reason is because you must ensure that the subclass's version of the method can be called from all contexts that the superclass's version can be called from. I find it interesting this is allowed in C++.Treasatreason
Well..lets say that C++ allows you to hang yourself if you are hell bent on it.Subbasement
A
12

The behavior is correct. Whenever you declare your function as "virtual", you instruct the compiler to generate a virtual call, instead of the direct call to this function. Whenever you override the virtual function in the descendant class, you specify the behavior of this function (you do not change the access mode for those clients, who rely on the "parent's" interface).

Changing the access mode for the virtual function in the descendant class means that you want to hide it from those clients, who use the descendant class directly (who rely on the "child's" interface).

Consider the example:

void process(const A* object) {
   object->func();
}

"process" function relies on the parent's interface. It is expected to work for any class, public-derived from A. You cannot public-derive B from A (saying "every B is A"), but hide a part of its interface. Those, who expect "A" must receive a fully functional "A".

Autolysin answered 30/6, 2009 at 8:16 Comment(5)
I agree. Just that compiler converting a Private function in to public seemed unacceptable. But true "You cannot public-derive B from A (saying "every B is A"), but hide a part of its interface. Those, who expect "A" must receive a fully functional "A"." Thanks for the explanation.Pickar
Thanks, SadSido. You seem to be the only one that addressed "public-derived". I had actually forgotten that there was more than just public inheritance because I only ever use public inheritance. It is the nature of the public inheritance that allows this behavior - which makes more sense. I thought this was the best explanation. Thank you.Treasatreason
Thank you, all. Yet, there is one issue on this question, that bothers me. I can have a pointer to the Child object and call its private method simply by static_casting it to Parent object... Well, I guess, to static_cast in this case means "to rely on Parent interface"...Autolysin
I might add that requiring the compiler to forbid calling the private virtual function of B through a->func would almost be asking for the impossible. Violation on acces rights are checked at compile time, but this access takes place at run time. And even if you were to fill the executale with runtime checks, would it really be desirable that the user of a program sees the program halting with an access violation error?Rhinarium
Adding on to this - consider the alternative. If a base class interface is modified by the derived class, functions that rely on interfaces might crash. For example, say void doFunc (A* ptr) { ptr->func(); } was written when A was written. Subsequently, B extends A and modifies it's interface. Since B* IS-A A*, the compiler allows us to pass doFunc (&bObj). This would crash if B was allowed to change func()'s access mode. In effect, programming with interfaces is the reason this happens.Ling
E
13

This is well-defined behavior. If a were a B* this wouldn't compile. The reason is that member access is resolved statically by the compiler, not dynamically at run-time. Many C++ books suggest that you avoid coding like this because it confuses less experienced coders.

Estevez answered 30/6, 2009 at 4:31 Comment(5)
What do you mean that member access is resolved statically by the compiler? The whole point of the vtable is that the compiler does not know which function to call at compile time... so it has to look it up at run time. Can you clarify?Treasatreason
@Tom: AT the compile time the semantics of your program is checked. You are calling func() using A* which is a valid call. The comipler has no way to know that this call resolves in to a private member function call at runtime. During runtime, there is no concept of private or public, it is just a function call. If you had used B* instead of A* then the call to func() would have been invalid and compiler would have flagged it as an error.Subbasement
@Naveen: Thank you. I guess I was forgetting the compiler didn't keep that access info at runtime... to me, this seems like undesired behavior. Especially since you can just cast B's to A's to try to access the private function. Now this makes sense... I just don't like it :-).Treasatreason
Saying "avoid coding like this because it confuses less experienced coders" implies that there are reasons why you might want to do this if it weren't for "less experienced coders". Are there?Bozen
@Laurence: I remember reading something about why one would want to do this, and suggestions to avoid it. I have since forgotten the reason why this could be useful.Estevez
A
12

The behavior is correct. Whenever you declare your function as "virtual", you instruct the compiler to generate a virtual call, instead of the direct call to this function. Whenever you override the virtual function in the descendant class, you specify the behavior of this function (you do not change the access mode for those clients, who rely on the "parent's" interface).

Changing the access mode for the virtual function in the descendant class means that you want to hide it from those clients, who use the descendant class directly (who rely on the "child's" interface).

Consider the example:

void process(const A* object) {
   object->func();
}

"process" function relies on the parent's interface. It is expected to work for any class, public-derived from A. You cannot public-derive B from A (saying "every B is A"), but hide a part of its interface. Those, who expect "A" must receive a fully functional "A".

Autolysin answered 30/6, 2009 at 8:16 Comment(5)
I agree. Just that compiler converting a Private function in to public seemed unacceptable. But true "You cannot public-derive B from A (saying "every B is A"), but hide a part of its interface. Those, who expect "A" must receive a fully functional "A"." Thanks for the explanation.Pickar
Thanks, SadSido. You seem to be the only one that addressed "public-derived". I had actually forgotten that there was more than just public inheritance because I only ever use public inheritance. It is the nature of the public inheritance that allows this behavior - which makes more sense. I thought this was the best explanation. Thank you.Treasatreason
Thank you, all. Yet, there is one issue on this question, that bothers me. I can have a pointer to the Child object and call its private method simply by static_casting it to Parent object... Well, I guess, to static_cast in this case means "to rely on Parent interface"...Autolysin
I might add that requiring the compiler to forbid calling the private virtual function of B through a->func would almost be asking for the impossible. Violation on acces rights are checked at compile time, but this access takes place at run time. And even if you were to fill the executale with runtime checks, would it really be desirable that the user of a program sees the program halting with an access violation error?Rhinarium
Adding on to this - consider the alternative. If a base class interface is modified by the derived class, functions that rely on interfaces might crash. For example, say void doFunc (A* ptr) { ptr->func(); } was written when A was written. Subsequently, B extends A and modifies it's interface. Since B* IS-A A*, the compiler allows us to pass doFunc (&bObj). This would crash if B was allowed to change func()'s access mode. In effect, programming with interfaces is the reason this happens.Ling
C
3

Well, you are calling A::func() which is public though in a B object it is overridden by B::func(). This is a common pattern with the following implications:

  • func is not intended to be called on derived B objects

  • func cannot be overridden in classes derived from B

Cystine answered 30/6, 2009 at 4:36 Comment(10)
If you say "func is not intended to be called on derived B objects", then why does it work?Treasatreason
@Tom: Calling on A *a = new B() works because you're calling A::func(). Calling on B *b = new B() does not compile since you're calling private B::func() outside B.Cystine
... outside B or its friends, that is.Cystine
@laalto: I unserstand that. But it seems that Ashwin is saying a->func is calling B::func. This seems to break the access level, because whenever I want to invoke the private B::func, I can just cast a B to an A and then invoke func(). It seems like I shouldn't be able to do that. It seems like you are saying that the purpose of changing the access level to private in a subclass is so that sub-sub-classes cannot override func() AND so that if you have something that is statically type B, you cannot invoke func(). But the method still isn't really private if you can work around by casting :-(.Treasatreason
... basically, it seems wrong that you can work around access levels through polymorphism. This is very counter intuitive to me.Treasatreason
@Tom, @Naveen, @laalto: I agree that the access level specifiers are not used during runtime and is only for the compiler but VTABLE is created by the compiler. The private function should not have been added to the VTABLE causing a loophole. Even for the above statement "func cannot be overridden in classes derived from B" is not working :( - i created class C inheriting B and created a public void func() in it and did "A *a = new C()" assignment and the output was "C::func called"!!!! Maybe g++ compiler is broken...Pickar
@Tom: C++ and intuition do not always go hand in hand.Cystine
@Ashwin: It is not true that the compiler shouldn't add the private function to the vtable... there are contexts from which it is valid to invoke the private function. If you moved your main() code into a function in the class B, for example, the private function call would be valid. It seems the best way for the compiler to enforce the access level is to take the java approach: do not allow overridden method access level to become more private.Treasatreason
@Ashwin: Maybe you should edit your original question and add your class C findings?Treasatreason
@Tom: Thanks. I have added the same. As you are saying Java's approach seems to be the best. I wasnt saying VTABLE should never have private functions - just that there should be some logic to check for access level changes and selectively populate the VTABLE. But I agree it would muddle things up.Pickar

© 2022 - 2024 — McMap. All rights reserved.