David Hovsepyan
David Hovsepyan

Reputation: 626

Why private virtual member function of a derived class is accessible from a base class

Consider the following snippet of code:

#include <iostream>
    
class Base {
public:
  Base() {
      std::cout << "Base::constr" << std::endl;
      print();
  }
  virtual ~Base() = default;

  void print() const { printImpl(); }
  
private:
    virtual void printImpl() const {
        std::cout << "Base::printImpl" << std::endl;
    }
};

class Derived : public Base {
public:        
    Derived() {
        std::cout << "Derived::constr" << std::endl;
    }
    
private:
    void printImpl() const override {
        std::cout << "Derived::printImpl" << std::endl;
    }
};

int main() {
    Base* ptr = new Derived();
    ptr->print();
    delete ptr;
}

The above code will print the following:

Base::constr
Base::printImpl
Derived::constr
Derived::printImpl

but I don't understand why printImpl private function is accessible from the base's print function. In my understanding this pointer implicitly passed to the print function holds the derived object's address, but I thought private member functions could be called ONLY from the member functions (and from friend functions) of the same class and here, Base class is not the same class as Derived, although there is an is a relationship.

Upvotes: 1

Views: 610

Answers (4)

user12002570
user12002570

Reputation: 1

Case 1

Here we consider the statement:

Base* ptr = new Derived();

These are the effects of the above statement:

Step 1) An object of type Derived is created on the heap using the default ctor Derived::Derived().

Step 2) Before entering the body of the default ctor Derived::Derived(), the compiler imlicitly calls the default ctor Base::Base(). Thus we get the output:

Base::constr

Step 3) Next, the statement print() inside Base::Base() is encountered. This statement is equivalent to writing:

this->print();

Hence, the print() function of class Base is called.

Step 4) Inside the print member function of Base, the call statementprintImpl(); is encountered. This statement is equivalent to writing:

this->printImpl();

But note that currently, the construction of the subobject of type Base is going on. Meaning the Derived part of the whole object has not yet been constructed. So even though the printImpl is a virtual member function and the call is made through a pointer, the virtual mechanism is disabled at this point. And thus the this->printImpl() calls the Base::printImpl version instead of the derived version of printImpl. This happened because the derived class has not yet been initialized. Thus we get the output:

Base::printImpl

Step 5) Finally, the body of the default ctor Derived::Derived() is executed. And hence we get the output:

Derived::constr

Step 6) The pointer ptr points to the Base part of the newly created derived object.


Case 2

Here we consider the statement:

 ptr->print();

Now, from this pionter's documentation:

When a non-static class member is used in any of the contexts where the this keyword is allowed (non-static member function bodies, member initializer lists, default member initializers), the implicit this-> is automatically added before the name, resulting in a member access expression (which, if the member is a virtual member function, results in a virtual function call).

So, when you wrote:

ptr->print();  //this is equivalent to writing (*ptr).print();

The above statement is equivalent to writing:

(*ptr).print();

This means that the address of the object to which the pointer ptr points(which is nothing but ptr itself) is implicitly passed to the implicit this parameter of the print member function.

Now, inside the print member function, the statement containing the call to printImpl() is equivalent to:

this->printImpl();

according to the quoted statement at the beginning of my answer. And from the same quoted statement, since the member printImpl is a virtual member function, the expression this->printImpl() results in a virtual function call, meaning it results in calling the derived class printImpl function. And hence we get the output:

Derived::printImpl

Upvotes: 0

einpoklum
einpoklum

Reputation: 131445

First, as @Eljay notes - printImpl() is a method, albeit virtual, of the Base class. So, it's accessible from the base class. Derived merely provides a different implementation of it. And the whole point of virtual functions is that you can call a subclass' override using a base class reference or pointer.

In other words, private only regards access by subclasses; it's meaningless to keep something private from a class' base class: If a method is at all known to the base class, it must be a method of the base class... a virtual method.


Having said all that - note that the Derived version of printImpl() is effectively inaccessible from print() - when it's invoked within the base class constructor. This is because during that call, the constructed vtable is only that of Base, so printImpl points to Base::printImpl.

I thought private member functions could be called ONLY from the member functions of the same class

And indeed, print() is a member of Base, which invokes printImpl() - another method of Base.

Upvotes: 1

ALX23z
ALX23z

Reputation: 4703

The reason that in the code you have Base::printImpl printed is because it is called from constructor of Base. The derived class wasn't constructed yet, so all calls to virtual functions will be referred to Base's versions.

After construction the calls to print will akways redirect towards derived class's version. Whether functions are marked private, public, protected is inconsequential here.

Upvotes: -1

Aleksandr Medvedev
Aleksandr Medvedev

Reputation: 8968

The private function of base class is not accessible in the derived class, but it can be overriden, since these two are separate concepts.

For the print call from the constructor of the Base class, there is a following rule:

In a constructor, the virtual call mechanism is disabled because overriding from derived classes hasn’t yet happened. Objects are constructed from the base up, “base before derived”.

P.S. In general calls to virtual functions from constructors or destructors better be avoided

Upvotes: 1

Related Questions