为什么派生 class 的私有虚拟成员函数可以从基 class 访问

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

考虑以下代码片段:

#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;
}

以上代码将打印以下内容:

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

但我不明白为什么 printImpl private 函数可以从基础的 print 函数访问。在我的理解中,隐式传递给 print 函数的 this 指针保存派生对象的地址,但我认为私有成员函数只能从成员函数(以及 friend 函数)调用相同的 class 而这里,Base class 与 Derived 不同 class,尽管存在 is a 关系。

基 class 的私有函数在派生 class 中不可访问,但可以覆盖它,因为这两个是不同的概念。

对于 Base class 的构造函数的 print 调用,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。一般来说,最好避免从构造函数或析构函数调用虚函数

在代码中打印 Base::printImpl 的原因是因为它是从 Base 的构造函数中调用的。派生的 class 尚未构建,因此所有对虚函数的调用都将引用 Base 的版本。

构建后,对 print 的调用将 akways 重定向到派生的 class 版本。函数是否标记为 private, public, protected 在这里无关紧要。

首先,正如@Eljay 指出的那样 - printImpl()Base class 的一种方法,尽管它是虚拟的。因此,可以从基础 class 访问它。 Derived 只是提供了一种不同的 实现方式 。虚函数的全部意义在于,您可以使用基 class 引用或指针调用 subclass' 覆盖。

换句话说,private只考虑subclasses的访问;从 class' 基础 class 中保留某些东西 private 是没有意义的:如果一个方法完全为基础 class 所知,那么它一定是一个方法 of base class...一个虚方法。


说了这么多 - 请注意 printImpl()Derived 版本在 print() 中实际上是 无法访问 - 当它在基础 class 构造函数。这是因为在那个调用中,构造的vtable只是Base的vtable,所以printImpl指向Base::printImpl

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

事实上,print()Base 的成员,它调用 printImpl() - Base 的另一个方法。

案例一

这里我们考虑语句:

Base* ptr = new Derived();

以上语句的效果如下:

步骤 1) 使用 默认构造函数 [=22= 在堆上创建类型 Derived 的对象].

步骤 2) 在进入默认 ctor Derived::Derived() 的主体之前,编译器隐式调用默认 ctor Base::Base()。因此我们得到输出:

Base::constr

步骤3) 接下来遇到Base::Base()里面的语句print()。这个语句相当于写:

this->print();

因此调用了classBaseprint()函数

Step 4) 在Baseprint成员函数内部,遇到调用语句printImpl();。这个语句相当于写:

this->printImpl();

但请注意,目前正在构建Base类型的子对象。这意味着整个对象的 Derived 部分尚未构建。所以即使printImpl是一个虚成员函数,通过指针调用,虚机制在这时被禁用观点。因此 this->printImpl() 调用 Base::printImpl 版本而不是 printImpl 的派生版本。发生这种情况是因为派生的 class 尚未初始化。因此我们得到输出:

Base::printImpl

步骤5) 最后,默认ctor的主体Derived::Derived()被执行。因此我们得到输出:

Derived::constr

步骤6)指针ptr指向新建派生对象的Base部分


案例二

这里我们考虑语句:

 ptr->print();

现在,来自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).

所以,当你写道:

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

以上语句等同于写法:

(*ptr).print();

这意味着指针ptr指向的对象的地址(就是ptr本身)是隐式传递print成员函数的隐式this参数。

现在,在 print 成员函数中,包含对 printImpl() 的调用的语句等效于:

this->printImpl();

根据我回答开头的quoted statement。从同一条引用语句中,由于成员 printImpl 是一个 虚拟成员函数 ,表达式 this->printImpl() 导致 虚拟函数调用 ,这意味着它会导致调用派生的 class printImpl 函数。因此我们得到输出:

Derived::printImpl