17
# include <iostream>
using namespace std;

class A
{
    public:
    virtual void f()
    {
        cout << "A::f()" << endl;
    }
};
class B:public A
{
    private:
    virtual void f()
    {
        cout << "B::f()" << endl;
    }
};
int main()
{
    A *ptr = new B;
    ptr->f();
    return 0;
}

This code works correctly and prints B::f(). I know how it works, but why is this code allowed?

Fred Nurk
  • 13,952
  • 4
  • 37
  • 63
Mihran Hovsepyan
  • 10,810
  • 14
  • 61
  • 111

6 Answers6

14

Access control is performed at compile time, not runtime. There's no way in general for the call to f() to know the runtime type of the object pointed to by ptr, so there's no check on the derived class's access specifiers. That's why the call is permitted.

As for why class B is permitted to override using a private function at all - I'm not sure. Certainly B violates the interface implied by its inheritance from A, but in general the C++ language doesn't always enforce inheritance of interface, so the fact that it's Just Plain Wrong doesn't mean C++ will stop you.

So I'd guess that there's probably some use case for this class B - substitution still works with dynamic polymorphism, but statically B is not a substitute for A (e.g. there can be templates that call f, that would work with A as argument but not with B as argument). There may be situations where that's exactly what you want. Of course it could just be an unintended consequence of some other consideration.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • 1
    It's no LSP violation, because a B object can be used everywhere an A can. Such contexts trigger an implicit Derived-to-Base conversion, possibly slicing the extra parts. After such a conversion, `B::f` is accessed as `A::f`, which is public. – MSalters Feb 14 '11 at 13:00
  • 2
    @MSalters: there are two different kinds of substitution in C++, because there are (at least) two different kinds of polymorphism in C++. We can separately evaluate whether LSP applies to each. If your B object is e.g. passed by value in a function call which uses template argument deduction to instantiate the function from a template, then it might well not be sliced, and would indeed access `f` as `B::f`. B doesn't satisfy all the concepts that A satisfies, basically, so objects are only substitutable once the type has been stripped (e.g. using an A*, or converting to A). – Steve Jessop Feb 14 '11 at 13:09
  • 1
    LSP is an OOP principle that constrains _named conformance_. The template context isn't a normal OOP context. It's generic programming, which generally ignores inheritance. Instead, it uses _structural conformance_. Therefore the LSP principle from OOP doesn't apply to template parameters. – MSalters Feb 14 '11 at 13:09
  • Also, just because B is convertible to A still doesn't make B objects substitutes for A objects in C++. An object of type A can be passed to a function that takes `A&`, or one that takes `C` where A converts to `C`. An object of type B can't be passed to either without an explicit conversion first (and it won't be the same object passed except with `A &a_ref = b_obj`). – Steve Jessop Feb 14 '11 at 13:12
  • @MSalters: If you restrict LSP as applicable only to dynamic polymorphism (with pointers or references explicitly typed as the base class) then OK, but if you claim that a derived->base slice is in any way relevant, then I think template deduction should also be relevant. But quite possibly I shouldn't take Liskov's name in vain when talking about them, if she's not interested in static polymorphism. – Steve Jessop Feb 14 '11 at 13:15
  • "can't be passed to either" - this would be true if my first example had been `const C&` instead of `A&`. The point is just that an implicit conversion isn't an invisible free bonus in C++, but "a B object can be used anywhere an A can because B converts to A" would only be true if it were. A B object can be used in *some* places an A can, most importantly as the referand of a pointer/reference to A through which virtual calls are made. Elsewhere it is not *necessarily* a substitute. – Steve Jessop Feb 14 '11 at 13:25
  • The problem with applying LSP to structural conformance is that LSP relies on the notion of co- and contravariance of argument types and return values. Now you can recursively apply structural conformance to define co- and contravariance, but that's not how C++ defines it. Things get hoary if you go that way ;) – MSalters Feb 14 '11 at 13:29
  • @MSalters: I'm prepared to believe it. I suppose what I want then is a separate substitution principle. Hers says, "when you inherit publicly, your object is passed around via pointer-to-A, so it must implement the virtual interface". Mine says, "when you inherit publicly, implement concepts subject to the limits imposed by the C++ language". e.g. if a template function takes A and returns `vector`, then LSP-wise you're inherently screwed in any attempt to substitute B. But in plenty of other situations templates *could* work if B implements A's non-virtual interface too. – Steve Jessop Feb 14 '11 at 13:43
  • Although I guess a lot of the problem goes away if you follow the advice in C++ that base classes should *always* be abstract. Then the non-Liskov-applicable substitutions that we're talking about mostly don't happen, because you never use base classes by value anywhere. Derived-base conversion is certainly out of the question. – Steve Jessop Feb 14 '11 at 13:45
2

This code is allowed because f is public in A's interface. A derived class cannot change the interface of a base class. (Overriding a virtual method isn't changing the interface, nor is hiding members of a base, though both can appear to do so.) If a derived class could change a base's interface, it would violate the "is a" relationship.

If the designers of A want to make f inaccessible, then it should be marked protected or private.

Fred Nurk
  • 13,952
  • 4
  • 37
  • 63
0

Function access control check happens in later stage of a c++ function call. The order in high level would be like name lookup, template argument deduction(if any), overload resolution, then access control(public/protect/private) check.

But in your snippet, you were using a pointer to base class and function f() in base class is indeed public, that's as far as compiler can see at compiler time, so compiler will certain let your snippet pass.

A *ptr = new B;
ptr->f();

But all those above are happens at compile time so they are really static. While virtual function call often powered by vtable & vpointer are dynamic stuff which happens at runtime, so virtual function call is orthogonal to access control(virtual function call happens after access control),that's why the call to f() actually ended B::f() regardless is access control is private.

But if you try to use

B* ptr = new B;
ptr->f()

This will not pass despite the vpointer & vtable, compiler will not allow it to compile at compile time.

But if you try:

B* ptr = new B;
((static_cast<A*>(ptr))->f();

This would work just fine.

BenMorel
  • 34,448
  • 50
  • 182
  • 322
RoundPi
  • 5,819
  • 7
  • 49
  • 75
0

Your base class is defining the interface for all the inherited children. I do not see why it should prevent the mentioned access. You can try deriving a class down from 'B' and use the Base interface to call , which would result in an error.

Cheers!

Ricko M
  • 1,784
  • 5
  • 24
  • 44
0

In addition to Steve's answer:

  • B is publically derived from A. That implies Liskov substitutability
  • Overriding f to be private seems to violate that principle, but actually it does not necessarily - you can still use B as an A without the code getting in the way, so if the private implementation of f is still okay for B, no issues
  • You might want to use this pattern is B should be Liskov substitutable for A, but B is also the root of another hierachy that is not really related (in Liskov-substitutable fashion) to A, where f is no longer part of the public interface. In other words, a class C derived from B, used through a pointer-to-B, would hide f.
  • However, this is actually quite unlikely, and it would probably have been a better idea to derive B from A privately or protected
Joris Timmermans
  • 10,814
  • 2
  • 49
  • 75
-1

Pretty much like in Java, in C++ you can increase the visibility of methods but not decrease it.

Sambatyon
  • 3,316
  • 10
  • 48
  • 65
  • 1
    I don't know about Java, but B::f does hide A::f, and [B::f is private](http://codepad.org/GhjYTeqY). – Fred Nurk Feb 14 '11 at 11:10
  • In Java, you can't make an inherited method private in the derived class, it will not compile. – Ali Feb 18 '11 at 12:00