Viktor
Viktor

Reputation: 1054

Why access qualifier is not considered when virtual function is overridden?

Following code prints "I'm B!". It's a bit strange because B::foo() is private. About A* ptr we can say that its static type is A (foo is public) and its dynamic type is B (foo is private). So I can invoke foo via pointer to A. But this way I have access to private function in B. Can it be considered as encapsulation violation?

Since access qualifier is not part of class method signature it can lead to such strange cases. Why does in C++ access qualifier is not considered when virtual function is overridden? Can I prohibit such cases? What design principle is behind this decision?

Live example.

#include <iostream>

class A
{
public:
    virtual void foo()
    {
        std::cout << "I'm A!\n";
    };
};

class B: public A
{
private:
    void foo() override
    {
        std::cout << "I'm B!\n";
    };
};

int main()
{
    A* ptr;
    B b;

    ptr = &b;

    ptr->foo();
}

Upvotes: 5

Views: 391

Answers (2)

Vasiliy Galkin
Vasiliy Galkin

Reputation: 2036

You have multiple questions, so I'll try to answer them one-by-one.

Why is in C++ access qualifier not considered when virtual function is overridden?

Because access qualifiers are taken into account by the compiler after all overload resolutions. Such behavior is prescribed by the Standard.

For example, see on cppreference:

Member access does not affect visibility: names of private and privately-inherited members are visible and considered by overload resolution, implicit conversions to inaccessible base classes are still considered, etc. Member access check is the last step after any given language construct is interpreted. The intent of this rule is that replacing any private with public never alters the behavior of the program.

The next paragraph described the behavior demonstrated by your example:

Access rules for the names of virtual functions are checked at the call point using the type of the expression used to denote the object for which the member function is called. The access of the final overrider is ignored.

Also see the sequence of actions listed in this answer.

Can I prohibit such cases?

No.

And I don't think you will ever be able to do so, because there's nothing illegal in this behavior.

What design principle is behind this decision?

Just to clarify: by "decision" here I imply the prescription for the compiler to check the access qualifiers after overload resolution. The short answer: to prevent surprises when you're changing your code.

For more details let's assume you're developing some CoolClass which looks like this

class CoolClass {
public:
  void doCoolStuff(int coolId); // your class interface
private:
  void doCoolStuff(double coolValue); // auxiliary method used by the public one
};

Assume that compiler can do overload resolution based on public/private specifiers. Then the following code would successfully compile:

CoolClass cc;
cc.doCoolStuff(3.14); // invokes CoolClass::doCoolStuff(int)
  // yes, this would raise the warning, but it can be ignored or suppressed 

Then at some point you discover that your private member function is actually useful for the class client and move it to "public" area. This automatically changes the behavior of the preexisting client code, since now it invokes CoolClass::doCoolStuff(double).

So the rules of applying access qualifiers are written in a manner that does not allow such cases, so instead you will get the "ambiguous call" compiler error in the very beginning. And virtual functions are no special case for the same reason (see this answer).

Can it be considered as encapsulation violation?

Not really. By converting pointer to your class into a pointer to its base class you're actually saying: "Herewith I would like to use this object B as if it's an object A" - which is perfectly legal, because the inheritance implies "as-is" relation.

So the question is rather, can your example be considered as violating contract prescribed by the base class? It seems that yes, it can.

See the answer to this question for alternative explanation.

P.S.

Don't get me wrong, all this doesn't mean at all that you shouldn't use private virtual functions. On the contrary, it's often considered as a good practice, see this thread. But they should be private from the very base class. So again, the bottom line is, you should not use private virtual functions to break public contracts.

P.P.S. ...unless you deliberately want to force client to use your class via the pointer to interface / base class. But there are better ways for that, and I believe the discussion of those lies beyond the scope of this question.

Upvotes: 3

user0042
user0042

Reputation: 8018

Access qualifiers like public, private, etc. are a compile time feature, while dynamic polymorphism is a runtime feature.

What do you think should happen at runtime when a private override of a virtual function is called? An exception?

Can it be considered as encapsulation violation?

No, since the interface is already published through the inheritance, it isn't.

It's perfectly fine (and might be intended), to override a public virtual function from the base class with a private function in the derived class.

Upvotes: 2

Related Questions