为什么派生 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();
因此调用了classBase
的print()
函数
Step 4) 在Base
的print
成员函数内部,遇到调用语句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
考虑以下代码片段:
#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();
因此调用了classBase
的print()
函数
Step 4) 在Base
的print
成员函数内部,遇到调用语句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